Top Tools

Making a Markdown-Based Blog with Vue and Gridsome

Matt Maribojoc

Matt Maribojoc · 12 min read

Sep 19, 2021

Using Vue with Gridsome is one of the easiest ways to create static websites. Gridsome is a powerful JAM-stack framework for Vue that allows to pull in data from different sources (Markdown files, Netlify, or any Headless CMS), transform that data using GraphQL, and then procedurally generate a static site using Vue templates.

In this article, we’re going to be building a blog that allows us to create new posts just by adding a Markdown file! Let’s take a look at what that might look like…

If we want to make a post, all we have to do is create a markdown file with a YAML header like this…

And then Gridsome will listen to the way that we want to handle this data and create a dedicated page for our content!

I think that using template files is one of the most straightforward ways to start to understand the power of Gridsome, so let’s jump right in!

What is Gridsome?

Like I mentioned in the intro, Gridsome is a JAM-stack framework for Vue that allows us to create static websites by pulling in data from a variety of sources.

It allows us to…

  • dynamically generate routes based on our file structure

  • assign different data sources to Vue templates for rendering

  • query our different data sources using GraphQL

  • easily deploy our static site

Under the hood, it generates a static site that also runs a Vue SPA so you can build both static sites and dynamic sites in Gridsome! It also uses Vue Router for its routing and the Gridsome ecosystem has tons of different plugins that allow you to customize your site and connect to dozens of different data sources to build your site.

For this tutorial, we’re going to be using the Vue 2 version of Gridsome, since Gridsome is still developing its Vue 3 version.

Installing Gridsome and creating our project

Now that we have a better idea of what Gridsome can be used for, let’s install it. So in our terminal, let’s run…

Installing the Gridsome CLI

bash
npm install --global @gridsome/cli @gridsome/transformer-remark

OR

yarn global add @gridsome/cli @gridsome/transformer-remark

We need @gridsome/transformer-remark to be able to parse our Markdown files!

Then, to build and run our Gridsome project, all we have to run is…

Let's run our project!

bash
gridsome create my-gridsome-blog
cd my-gridsome-blog
gridsome develop

Then, if we go into our browser and head over to http://localhost:8080, we can see the boilerplate Gridsome site!

Alright – now let’s take a look at how our file structure inside src

  • components – any typical Vue components you would want to use as building blocks live here

  • pages – more static page level components, this will only exist once on your site

  • templates – templates for your dynamic component, such as Blog Posts, Product Pages, and more

If we open up our code and go to src/pages/Index.vue – we’ll see most of the content that’s displayed on our home page. And this file is just your typical Vue component with a template, script, and style sections.

There are two important things to note:

First, Index.vue is using g-image – a built-in component in Gridsome that creates a progressive image. Meaning on page load, a smaller base64 version of the image is displayed, then using IntersectionObservers to detect the image’s position to the viewport, it will load in the full image as needed.

This helps lower our page load times and makes our static site even faster.

Second, our whole component is wrapped in a Layout global component, which is defined in main.js. The layout itself is found in layouts/Default.vue. This is where that header section is coming from on our app.

This is also where we can find our first GraphQL query. And if you’re using VSCode and aren’t getting proper syntax highlighting, check out Gridsome’s guide on how to add GraphQL support in a Vue SFC.

There are two ways to run GraphQL queries in Gridsome:

  • <page-query> – which should be used in pages and templates

  • <static-query> – which should be used in components (like our Layout)

This is a simple GraphQL query that is looking for the metadata object, and then also querying for its siteName property. The result will be able under $static.metadata.siteName as we can see higher up in the component.

One of my favorite parts about using Gridsome is that even though we will be using GraphQL, you don’t need to be an expert in order to understand what’s going on. I’ve never used GraphQL before this, but I had a pretty easy time diving into the code and getting it running.

But back to siteName, we can see this property being created our gridsome.config.js file. If we open that up we’ll see our siteName being set up.

And if we want to change the siteName, we can change this file, so let’s say we want to change it to LearnVue.

gridsome.config.js

javascript
module.exports = {
  siteName: "LearnVue",
  plugins: [],
};

BUT if we want to see our changes in our dev environment, we have to restart our server. After we do that, we should see our new title in our browser.

Awesome!

Adding Markdown files as a data source for Gridsome

In this gridsome.config.js file, we can also declare new data sources and map them to Vue templates.

For our example, we want to import markdown files into our data. But we can also pull from a ton of different sources. If you want to see more, check out Gridsome’s extensive plugin collection.

But to import Markdown files, we first have to run install @gridsome/source-filesystem by running

bash
npm install @gridsome/source-filesystem

OR

yarn add @gridsome/source-filesystem

Then, inside our config file, we can install our plugin like this…

gridsome.config.js

javascript
module.exports = {
  siteName: "LearnVue",
  plugins: [
    {
      use: "@gridsome/source-filesystem",
      options: {
        typeName: "Post",
        path: "./posts/**/*.md", // get any MD files in posts/
      },
    },
  ],
};

And here…

  • path will be the files we to include in our collection (for us we want all markdown files in a post folder)

  • typeName will be the name of the collection in our data

Now that we have all of that setup, there are really just two steps left.

  • Creating our markdown files in /post/

  • Creating a template to model this data

Let’s get right into these two tasks.

Creating our Markdown Blog Posts

Our markdown posts will follow the standard markdown file for writing. We can add headers, lists, code blocks, anything we typically would be able to.

So let’s create a posts folder in our root directory and make a markdown file called my-first-post.md

The only real Gridsome-specific thing is we want to add a YAML header that includes some more background information on our post.

So here’s what our first blog post will look like where we define its title, path, and some other metadata in a YAML header.

my-first-post.md

markdown
---
id: 1
title: "My First Post"
path: my-first-post
date: 2021-09-19
summary: "Hey everyone, check out my post"
tags: [ 'Blog', 'Gridsome' ]
---

This is our first blog post. Gridsome will turn this into a website!

Seeing if our data is being loaded

A cool feature of Gridsome is that we can explore our GraphQL data in a dedicated environment.

If we navigate to http://localhost:8080/___explore, we’ll see a GraphQL interface.

image

Now, to make sure our data is loading let’s look for our post that has the id that we set earlier in the YAML header.

Is our data loading???

graphql
query {
   post (id: 1) {
    	title,
        path,
    	date,
    	summary,
    	tags,
    	content
   }
}

And if we run this, we can see that we’re properly getting our post being loaded in from our Markdown file!

Fantastic! Now let’s build our template so we can display this…

Building our Vue Template for our Blog Post

Now that we have our data loading, let’s go ahead and define our template for our posts to map to.

Back inside gridsome.config.js, in order to dynamically generate routes, we can declare a templates property in our exports.

We want our Post template (which we will create in a minute) to map to paths that start with “/posts/“.

gridsome.config.js

javascript
module.exports = {
  siteName: "LearnVue",
  plugins: [
    {
      use: "@gridsome/source-filesystem",
      options: {
        typeName: "Post",
        path: "./posts/**/*.md", // get any MD files in posts/
      },
    },
  ],
  templates: {
    Post: "/posts/:path", // map our Post collection to paths!
  },
};

Okay! Let’s build this Post template by making a Post.vue component inside our src/templates folder.

Here, we want to create our standard template, script, and style sections. But we also want to build a <page-query> section where we will add our GraphQL query just like we saw in our default layout.

Post.vue

markup
<template> </template>
<page-query> </page-query>
<script> </script>
<style> </style>

Awesome – let’s start off with this page query. It will look something like this, where are querying for a post whose post matches the current path of our site! Then, we want to return all of its different fields, as well as a GraphQL generated field called timeToRead.

Post.vue

vue

<page-query>
query Post ($path: String!) {
	post (path: $path) {
		title,
		path,
		date,
		summary,
		tags,
		content,
		timeToRead
	}
}
</page-query>
 

Now, we’re ready to start building our template.

But first, let’s install moment – which is my favorite JS time utility library – using npm install moment and then include it inside of our main.js file like this.

Setup Moment Globally

javascript
import DefaultLayout from "~/layouts/Default.vue";
import moment from "moment";

export default function (Vue, { router, head, isClient }) {
  Vue.prototype.moment = moment;
  // Set default layout as a global component
  Vue.component("Layout", DefaultLayout);
}

The data that we just loaded in will be available in $page.post.{propertyName} so we can use all of our data to build our post component.

So here’s what I came up with the formatting of my blog post, but definitely get as creative as you want here!

Post.vue

vue
<template>
  <Layout>
    <header>
      <h1>{{ $page.post.title }}</h1>
      <h3 class="subheader">
        {{ moment($page.post.date).format('MMMM d, YYYY') }} &middot; {{
        $page.post.timeToRead }} min. read
      </h3>
    </header>
    <div v-html="$page.post.content" class="post__content" />
    <div class="tags">
      <span v-for="tag in $page.post.tags" :key="tag"> {{ tag }} </span>
    </div>
  </Layout>
</template>

<page-query>
query Post ($path: String!) {
	post (path: $path) {
		title,
		path,
		date,
		summary,
		tags,
		content,
		timeToRead
	}
}
</page-query>

<script></script>

<style>
  header .subheader {
    font-size: 1em;
    color: #aaaaaa;
  }
  .post__content {
    line-height: 200%;
  }
  .tags > span {
    background-color: #ddd;
    border-radius: 5px;
    padding: 5px;
    margin-right: 5px;
  }
</style>
 

If we navigate to the path for this article, we should see something like this…

And as a finishing touch, let’s make sure that our title in our browser matches the title we set for our article by adding this metaInfo in our Vue export default.

Post.vue - setting title metadata

javascript
export default {
  metaInfo() {
    return {
      title: this.$page.post.title,
    };
  },
};

Now, our title should be setting properly 🙂

Awesome!

Creating a list of all blog posts

A pretty standard use case is to have a large list of blog posts where users can navigate to individual posts that sound interesting.

So let’s take a look at how this works.

Let’s first add another Markdown post called my-second-post.md with some basic content.

my-second-post.md

markdown
---
id: 2
title: "My Second Post"
path: my-second-post
date: 2021-09-19
summary: "I made another one!"
tags: [ 'Blog', 'Take 2' ]
---

This is our second blog post. Still using the same template!

Now, let’s modify our pages/Index.vue file to display a list of all of the posts.

To get all of our posts, we’re going to need to create a <page-query> (remember: page, not static because this is a page level) with the following query to get some data from our posts.

Index.vue - page query to get all posts

vue
<page-query>
query {
	allPost {
		edges {
			node {
				path
				title
				date
				summary
			}
		}
	}
}
</page-query>
 

Then, we can simply loop over $pages.allPost.edges to list all the articles in our template.

Index.vue - list all of our posts!

markup
<template>
  <Layout>

    <div v-for="post in $page.allPost.edges" :key="post.node.path">
      <h2><router-link :to="post.node.path"> {{ post.node.title }} </router-link></h2>
      <h4>{{ moment(post.node.date).format('MMMM d, YYYY') }}</h4>
      <p>{{ post.node.summary }}</p>
    </div>

  </Layout>
</template>

Here’s our finished component with a little bit of styling.

Index.vue

vue
<template>
  <Layout>
    <!-- Learn how to use images here: https://gridsome.org/docs/images -->
    <div
      v-for="post in $page.allPost.edges"
      :key="post.node.path"
      class="post-list"
    >
      <h2>
        <router-link :to="post.node.path"> {{ post.node.title }} </router-link>
      </h2>
      <h4>{{ moment(post.node.date).format('MMMM d, YYYY') }}</h4>
      <p>{{ post.node.summary }}</p>
    </div>
  </Layout>
</template>

<page-query>
  query {
    allPost {
      edges {
        node {
          path
          title
          date
          summary
        }
      }
    }
  }
</page-query>

<script>
  export default {
    metaInfo: {
      title: "Hello, world!",
    },
  };
</script>

<style>
  .post-list {
    border: 2px solid #ccc;
    border-radius: 10px;
    margin-bottom: 10px;
    padding: 10px;
  }
  .post-list h2,
  .post-list h2 a {
    margin: 0;
    color: #27ae60;
    text-decoration: none;
  }
  .post-list h4 {
    margin: 0;
    color: #aaa;
  }
</style>
 

And if we navigate to our home page in our browser, we should see something like this!

We can now navigate around our app and see everything we need!

Final thoughts

As you can see, Gridsome is a really simple, yet powerful way to pull in data from a variety of sources and build static sites.

Using local markdown files is just one of the simplest ways to build your blog! If you’re looking into more advanced use cases, I’d definitely recommend looking into Netlify or another Headless CMS and integrating it using a Gridsome plugin.

Here are some other possible extensions for your own sites!

  • Add a site search system!

  • Make a more advanced template page

  • Include more custom fields on your YAML headers for article images!

I’d love to see what kind of things you all come up with! Leave your projects down in the replies below and as always, happy coding!

Check out the Github Repo


Join the LearnVue Community

Every week we send out exclusive content to thousands of developers on our mailing list. 100% Free.

Latest Posts

Top Tools

Making a Markdown-Based Blog with Vue and Gridsome

Use Vue with Gridsome is one of the easiest ways to create static websites from just Markdown files.

Matt Maribojoc · 12 min Read More
Top Tools

5 VueUse Library Functions That Can Speed Up Development

VueUse is an open-source project that provides Vue developers with a huge collection of essential Composition API utility functions for both Vue 2 and Vue 3.

Matt Maribojoc · 12 min Read More
Dev Tips

Lazy Load Components in Vue with defineAsyncComponent

Using Vue 3’s defineAsyncComponent feature lets us lazy load components - meaning they’re only loaded when they’re needed.

Matt Maribojoc · 7 min Read More
Essentials

The Beginner’s Guide to Vue Template Refs - with Vue 3 Updates

Vue Template Refs give our Javascript code a reference to easily access the template.

Matt Maribojoc · 5 min Read More