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 -l

Why 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