Building a static blog with vite-ssg
This site is a Vue single page app, but every route is also pre-rendered to static HTML at build time using vite-ssg. The blog rides on the same machinery, so each post ships as a…
This site is a Vue single page app, but every route is also pre-rendered to static HTML at build time using vite-ssg. The blog rides on the same machinery, so each post ships as a real HTML document with its title, meta tags, and structured data already in place.
The shape of a post
A post is two files. The body is a markdown file with the title as its first heading:
# Building a static blog with vite-ssg
This site is a Vue single page app, but every route is also
pre-rendered to static HTML at build time using vite-ssg.The metadata sits in a typed registry keyed by slug:
export const BLOG_POSTS = {
'building-a-static-blog-with-vite-ssg': {
seoTitle: 'Building a static blog with vite-ssg',
metaDescription: 'How this blog renders every post to static HTML.',
datePublished: '2026-06-02',
tags: ['vue', 'vite', 'seo'],
},
};Rendering happens once, at build time
A small Node script reads the markdown, renders it to HTML with marked, and highlights code blocks with Shiki. The result is written to a generated JSON file that the app imports directly:
import generated from './posts.generated.json';
export const blogPosts = generated.posts;
export function getPostBySlug(slug: string) {
return blogPosts.find((post) => post.slug === slug);
}Because the highlighting runs in the build, neither marked nor Shiki ends up in the browser bundle. Readers get pre-styled HTML, and the client only hydrates it.
Count your posts
Every post is a markdown file in one directory, so checking how many you have is a one line command. Pick your shell.
ls src/content/blog/posts/*.md | wc -limport glob
print(len(glob.glob("src/content/blog/posts/*.md")))(Get-ChildItem src/content/blog/posts/*.md).CountWhy bother
Three reasons:
- Crawlers see the full article on first byte, which is the whole point of an SEO blog
- The page is fast because there is no content fetch after load
- The author workflow is just writing markdown and opening a pull request
It is a small amount of build code in exchange for a blog that behaves like a static site and reads like a dynamic one.
Useful next step
