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 that enables the creation of various types of websites, including static ones. Empowered with abstractions like routing, server-side rendering (SSR), and 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 setup, simply utilize the [...slug].vue
as per the standard 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>
Whenever you navigate to domain.com/blog/first-article
, the [...slug].vue
file will correctly retrieve the associated content. Additionally, this setup preserves the functionality of existing routes and pages managed through Nuxt's file routing system.
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>
Final Thoughts
This setup allows to manage and display blog posts at a custom URL path enabling the use of markdown files to write blog posts, in a simple Nuxt.js application, leveraging the @nuxt/content
module for content management and still keeping the Nuxt's file routing system.
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/']
}
},