← All posts
2 min read#nuxt#vue#tooling

Building a static blog with Nuxt Content v3

How this site works under the hood: Markdown collections, SQLite-backed queries, and full static generation with Nuxt.

This site is a fully static Nuxt app. Every post is a Markdown file in the repo, and nuxt generate turns the whole thing into plain HTML. Here's the interesting part of the setup.

Collections are typed

Nuxt Content v3 replaced the old file-based querying with collections — you declare a schema, and frontmatter gets validated at build time:

const blogSchema = z.object({
  title: z.string(),
  description: z.string(),
  date: z.string(),
  tags: z.array(z.string()).default([]),
})

Typos in frontmatter now fail the build instead of silently rendering undefined. This is the single biggest quality-of-life win over v2.

Queries look like SQL because they are

Under the hood, content lives in SQLite. The query API compiles to SQL:

const posts = await queryCollection('blog_en')
  .order('date', 'DESC')
  .limit(3)
  .all()

Two languages, two collections

For a bilingual site, one collection per locale keeps things simple:

content/
  en/blog/    → collection blog_en, served at /blog
  uz/blog/    → collection blog_uz, served at /uz/blog

A tiny composable picks the right collection from the active locale, and the rest of the code never thinks about i18n.

The result

No server, no database in production, no CMS subscription. Just Markdown files, a git history, and static HTML that any CDN can serve.