How to setup a simple blog with Nuxt.js
I was looking for a simple Blog with file-based markdown or similar, to avoid databases or APIs layer and to reduce the load on the server and be able to just serve static files.
I know there are multiple solutions out there, but my focus was to keep it fast and simple.
I've been using platforms like WordPress and Jekyll while exploring JAMStack options too, but my preference for Vue.js led me to wonder if Nuxt might just be the thing since it has a module for that
Nuxt is a Vue.js Framework for building websites, including static ones. It comes with routing, server-side rendering (SSR), static site generation (SSG) and more.
1. Set up Nuxt and dependencies
The easy way to setup
npx nuxi@latest init content-app -t content
This installs nuxt and content module
2. The content directory
Content Module creates a content directory for us.
Where we'll put our Blog posts, as markdown files, easy as that:
/content
├ first-article.md
├ second-article.md
├ ....
3. Decide the structure of pages
I chose to use a multi-page blog setup, with index.vue as the landing/main page and a separate blog folder for the posts, to enable a different route like domain.com/blog.
/pages
├─ index.vue
├─ blog
│ ├─ [...slug].vue
Or, you can just put the [...slug].vue under pages to have the default single page pattern.
4. Setup the blog to fetch the right content
For a single-page blog, just use [...slug].vue with the default configuration.
<template>
<main>
<ContentDoc />
</main>
</template>
In the case of a multi-page blog setup, you should tweak the [...slug].vue file to fetch the right content.
<template>
<main class="container">
<ContentQuery :where="where" find="one">
<template #default="{ data }">
<ContentRenderer :value="data" />
</template>
<template #not-found>
<p>No article found.</p>
</template>
</ContentQuery>
</main>
</template>
<script setup>
const route = useRoute();
const slug = route.params.slug || [];
const where = { _path: "/" + slug.join("/") };
</script>
When you navigate to domain.com/blog/first-article, the [...slug].vue file fetches the matching content. Your existing routes and pages from Nuxt's file routing still work normally.
5. What happens when visiting domain.com/blog/ ?
We setup a content/index.md to serve as the main page for the blog. I've setup like this:
---
navigation: false
---
Blog
:the-index
The :the-index is a component at components/content/TheIndex.vue that renders a custom listing of posts sorted by date (date is a custom property in the markdown header file). Something like this:
<script>
const queryBuilder = queryContent()
.where({
navigation: { $not: false },
})
.sort({ date: -1 })
.find();
const { data: navigation } = await useAsyncData(
"navigation",
() => queryBuilder
);
// ... logic to use navigation and render listing (for-loop navigation)
</script>
Wrapping up
That's it. Blog posts as markdown files, served at a custom URL path with @nuxt/content, without breaking Nuxt's file routing.
To address this, there are many workarounds. My approach was modifying nitro config. To explicitly filter out pages that should be pre-rendered.
nitro: {
prerender: {
ignore: ['/blog/Nuxt'],
routes: ['/blog/']
}
},