Creating a Markdown-Based Blog Using Nuxt and Nuxt Content, but with AI! (feat. Claude Sonnet 4)

NuxtNuxt ContentAIClaude Sonnet 4
May 25, 2025
By Michał Hachuła
6 minutes read

Intro

Hello! It's my first post here, and I'm going to describe what I've encountered while creating a blog using Nuxt and Nuxt Content: what worked, what didn't, and what's the verdict on that stack.

Let's briefly introduce what we'll be using to create our blog. I'm quoting from the official docs.

Nuxt is a free and open-source framework with an intuitive and extendable way to create type-safe, performant and production-grade full-stack web applications and websites with Vue.js.

Nuxt Content is a module for Nuxt that provides a simple way to manage content for your application. It allows developers to write their content in Markdown, YAML, CSV, or JSON files and then query and display it in their application.

We'll be combining these two to create a simple yet functional blog using Markdown files.

Setup

To get started, first, we need to create a new Nuxt project and either select @nuxt/content to be installed now or we can add it manually later. For a package manager, I'm using pnpm, but you can use whichever one you prefer.

For detailed installation guide check out Nuxt's and Nuxt Content's docs.

pnpm create nuxt my-cool-blog

> Would you like to install any of the official modules?
[•] @nuxt/content – The file-based CMS with support for Markdown, YAML, JSON

 Nuxt project has been created with the v3 template. Next steps:
 cd my-cool-blog
 Start development server with pnpm run dev

Cool! After we have it all in place, we can proceed to actual creation of our blog, but with AI, of course.


Doing it with AI (feat. Claude Sonnet 4)

When I was planning this, I decided to take the newly released Claude Sonnet 4 for a spin. It should be simple enough, right? I create a naive prompt in Cursor without overthinking it:

Create a simple Markdown based blog in this Nuxt application using Nuxt Content.

At first glance, everything looked fine, and I was impressed that it was able to one-shot this. We got a new file content.config.ts with a newly defined collection:

import { defineContentConfig, defineCollection } from "@nuxt/content";

export default defineContentConfig({
  collections: {
    blog: defineCollection({
      type: "page",
      source: "**/*.md",
    }),
  },
});

The generated Vue component looked exactly like an example from the documentation:

<script setup>
const { data } = await useAsyncData('blog', () => {
  return queryContent('/').findOne()
})
</script>

<template>
  <pre>
    {{ data }}
  </pre>
</template>

Then I tried to add a new page into the newly created /content directory that should serve our Markdown pages, but there were no data found. After couple more tries of changing directories, filenames, and quite a lot of console.log statement I've realized what was the problem; queryContent was replaced by queryCollection in Nuxt Content version 3.

So yeah, we have an explanation for why it didn't work.

I also tried asking Claude a few times to fix the problem, but it just hallucinated and actually made everything worse, so beware (more on this later).

The actual Vue code using Nuxt Content version 3 should look like this:

<script setup>
const { data } = await useAsyncData("/blog/post", () => {
  return queryCollection("blog").path("/blog/post").first();
});
</script>

<template>
  <pre>
    {{ data }}
  </pre>
</template>

This code queries the blog collection (which was defined in content.config.ts) looks for a file on a path /blog/post and renders the post that you're reading at the moment :)


Nuxt Content - hot or not?

Before discussing a valuable lesson learned from using Claude Sonnet 4 while developing this blog, let's talk about Nuxt Content's limitations.

It's not hot because:

  1. At first glance, I thought I could just mount my Markdown files via a Docker volume and have them picked up by Nuxt Content without rebuilding the application (which on CI takes around 5 minutes). Unfortunately, that's not the case; each time I want to publish something new, I need to rebuild the app. This has an obvious performance benefit but delays the actual deployment.
  2. For some reason each modification of Vue component requires a full page reload, because after hot reload queryCollection returns undefined. That's definitely something I want to investigate and figure out what's causing this.

It's hot because:

  1. The provided ContentRenderer is great, easily customizable and does the job of rendering Markdown well.
  2. It has a lot of nice additions, such as Nuxt Studio and generally good documentation.

In summary, it's nice, but for me, having to rebuild the whole application to add a new Markdown file is an overkill. Also it's a bit of a pain to have to reload the page after each Vue component change (that's most likely some config bug). I'd say it's really good for serving a lot of existing files that don't change very often.


Closing Remarks

I'm satisfied with what I've build, but there were few important lessons learned along the way:

  1. LLMs hallucinate and can use outdated library versions if not guided properly. A good solution here would be to pass Nuxt Content's correct version docs into the context.
  2. Claude Sonnet 4 looks like a very capable model; however, it tends to create even more extraneous elements than Claude Sonnet 3.7.
  3. Nuxt Content is cool, but it would be very welcome to be able to create new pages without rebuilding the entire Nuxt app.
  4. It's a bit of a pain to have to reload the page after each Vue component change (that's most likely some config bug).

I hope this was an interesting read, and I can say that there will be more content coming. Thanks for your time :)