skip to content

Building a SEO Friendly Blog: Using Markdown and Front Matter

2020-09-25

Web Development

In this series' previous installment, I covered the basics of creating a static generated site with Nuxt. In today's installment, I'll show you how to use Markdown and Front Matter to develop rich content for your blog.

Before we write any code, the first thing we'll need to do is install a webpack loader for markdown. In your terminal, while in the project directory, run npm i frontmatter-markdown-loader to add this dependency to your package.json file. Next, we need to configure Nuxt to know how to load markdown files. In the root directory, create a file named nuxt.config.js and add the following code to extend webpack.

export default {
  target: 'static',
  build: {
    extend (config, ctx) {
      config.module.rules.push({
        test: /\.md$/,
        use: [{
          loader: 'frontmatter-markdown-loader'
        }]
      })
    }
  }
}

Now that we've configured our project to work with markdown, we can create our first article. Directly under the root directory, create a directory named articles. Inside of this directory, create a markdown file named dogs.md.

---
slug: dogs
title: 'Dogs are the Best Animal - Fight Me'
date: '2020-09-25'
tags: dogs,doggo,pupper,floofer,woofters
description: "Whether you call them dogs, doggos, puppers, floofers, or woofters, they are the best animal. I am willing to fight you over this if you say I'm wrong."
---

# Dogs are the Best Animal - Fight Me

Whether you call them dogs, doggos, puppers, floofers, or woofters, they are the best animal. I am willing to fight you over this if you say I'm wrong.

All doggos are a heckin' good fren. Anyone who disagrees is a monster.

You'll notice there's a bit of YAML code at the beginning of our markdown file. This is called front matter, and it's a way to include metadata with your markdown content.

The next step is to make our slug pull in the correct markdown file based on the URL parameter. While I would generally recommend not dynamically requiring any dependencies in your JavaScript code, we can safely do that here with our markdown file. We know this code will run at the time of generation, so if there are any missing files, we will catch them during the build process.

Inside of slug.vue update the asyncData function to be the following.

asyncData ({route}) {
  const { params: { slug } } = route
  const markdown = require(`~/articles/${slug}.md`)
  return {
    markdown
  }
}

This function grabs the URL's parameter, requires the matching markdown file, and makes the markdown file's results available to the template. The result is an object with two keys, html and attributes. The html key is what it sounds like; it is the HTML representation of your markdown file. The attributes key is an object of key/value pairs of all of the metadata you included with front matter.

The last part of wiring this up is to add it to the template. We'll be using v-html to do this, but this is a feature of Nuxt that you want to use with caution. This function will insert any HTML it's given into the page, which can leave you vulnerable to an XSS attack. Never use v-html to render user-generated content. This feature should only ever be used with trusted content.

To render the contents of the article, update the template to the following code:

<template>
  <article>
    <div v-html="markdown.html" />
  </article>
</template>

If you issue the command npm run dev and navigate to http://localhost/blog/dogs, you should see your article's content.

Now let's update our landing page to dynamically generate a list of articles based on our markdown files. Webpack allows for requiring files via context, meaning you can require all files that match a certain pattern. This is extremely useful for our case as we can get data about every article we've written without manually requiring each file individually.

In your index.vue file, add the following to the bottom of your file.

<script>
export default {
  asyncData () {
    const articles = []
    const r = require.context('~/articles', false, /\.md/)
    r.keys().forEach((key) => {
      articles.push(r(key))
    })
    return {
      articles
    }
  }
}
</script>

This script is loading all of the articles, putting them into an array, and making them available to the page's template. As we included a slug parameter in the article's metadata, we can use that to output an article list on the landing page. Here's the new code for our template.

<template>
  <div>
    <h1>Articles</h1>
    <ul>
      <li v-for="(article, index) in articles" :key="index">
        <nuxt-link :to="`/blog/${article.attributes.slug}`">{{ article.attributes.title }}</nuxt-link>
      </li>
    </ul>
  </div>
</template>

If you navigate to http://localhost:3000 you should now see a single link to the one article we've written. If you were to add another article to the articles directory, it would show up here too.

That wrap's it up for this part! In the next and final part of this series, we'll go into how to use the attributes we set in front matter to give search engines more context about our article.