Powering up static sites with Gridsome taxonomies

At Codegram we are already pretty sold on the JAMStack, especially using Gridsome, a Vue.js framework (for React devs, it would equivalent to Gatsby). Despite being just version v0.6.9 at the time I'm writing this, it's pretty much production-ready (unless you need i18n), and offers a great developer experience. We've built a few sites with it, most notably Full Stack Fest's and the very own where you are reading this.

Where Gridsome shines the most versus, let's say, a simple statically generated Nuxt site, is the ability to add a GraphQL data layer with just a few lines of code and 0 experience creating APIs. The first and most obvious need our website had that could be solved with this was the blog. So let's see how to create a blog with Gridsome!

First, we will need to install a couple of plugins: @gridsome/source-filesystem and @gridsome/transformer-remark. This will allow us to read markdown files and turn those into a GraphQL collection.

So, in gridsome.config.js file, we add the configuration for the plugin:

module.exports = {
  plugins: [
    {
      use: '@gridsome/source-filesystem',
      options: {
        path: 'src/blog/*.md',
        typeName: 'BlogPost',
        route: '/blog/:slug'
      }
    }
  ],
  transformers: {
    remark: {
      // we can add options here
    }
  }
}

Then, we create a blog.vue file inside src/pages, and we can query our blogposts. It's that easy!

<template>
  <Layout>
    <BlogPost
      v-for="post in $page.blogPosts.edges"
      :key="post.node.id"
      :post="post"
    />
  </Layout>
</template>

<static-query>
  query BlogPost {
    blogPosts: allBlogPost(sortBy: "published_at", order: DESC) {
      edges {
        node {
          title
          path
          image(width:640, quality: 75)
        }
      }
    }
  }
</static-query>

With the power of GraphQL, we only need to query the fields we need. Since it's the main blog page, we don't care about the content, so we only request title, path, and image (which we can even request in specific size and quality! 🤯).

What about each post page? With plain Vue or Nuxt we would need to create a route that with an id param, then get that param from the $route object, then query the post, set the data we get to the component state, probably adding a loading spinner while doing all that…

With Gridsome is much easier! We only need to create a BlogPost.vue in the templates folder (remember that BlogPost is the typeName we used when configuring our app), and write the query:

<template>
  <section>
    <h1>{{ $page.blogPost.title }}</h1>
    <!-- ... -->
  </section>
</template>

<page-query>
  query BlogPost ($path: String!) {
    blogPost: blogPost (path: $path) {
      title
      published_at
      author
      image(width:1024, quality: 75)
      content
    }
  }
</page-query>

As you see, the query gets a $path variable, that we pass along. But all that is done for you, no need to get the $route.params.id or anything!

That's only the beginning of what you can do with Gridsome, though. What good is a blog without tags, you might ask? Let's add them!

We need another GraphQL collection, but we don't want to have to create a file for each tag, right? Luckily there's an easy way! In gridsome.config.js we can add a reference to another typeName. The create option will create the collection without files:

    {
      use: '@gridsome/source-filesystem',
      options: {
        path: 'src/blog/*.md',
        typeName: 'BlogPost',
        route: '/blog/:slug',
        refs: {
          typeName: 'Tag',
          route: '/blog/tag/:slug',
          create: true
        }
      }
    }

Now we can query the tags the same way to create our tag list:

<page-query>
  query Tags {
    tags: allTag {
      edges {
        node {
          id
          path
        }
      }
    }
  }
</page-query>

But we will need to create a view for each tag, that lists the posts that have that tag, right? We create another template Tag.vue, and we can get the posts that the tag belongs to!

<page-query>
  query Tag($id: String!) {
    tag(id: $id) {
      id
      belongsTo(sortBy: "published_at", order: DESC) {
        edges {
          node {
            ... on BlogPost {
              title
              path
              image(width:640, quality: 75)
            }
          }
        }
      }
    }
  }
</page-query>

That ... on BlogPost bit might look a bit weird if you are not familiar with GraphQL, it's called an inline fragment.

Isn't it great to be able to create relationships that easily? Without a backend! To access the posts easily, I like to create computed properties so the code is more readable:

  computed: {
    posts() {
      return this.$page.tag.belongsTo.edges
    }
  }

This might not feel as much, but this truly brings a static site to the next level. We can create complex relationships between all the pieces that form our website. For example, we added related posts to some of our services. How? Adding tags as well!

    // gridsome.config.js
    {
      use: '@gridsome/source-filesystem',
      options: {
        path: 'src/services/*.json',
        route: '/services/:id',
        typeName: 'Service',
        refs: {
          tags: {
            typeName: 'Tag',
            route: '/blog/tag/:slug'
          }
        }
      }
    },

Since we already created the tags for our blog posts, there is no need to add the create: true again, it will match against current existing tags.

On our services query, we add the tags and their posts:

<page-query>
  query Service($path: String!) {
    service(path: $path) {
      title
      description
      tags {
        id
        belongsTo(
          sortBy: "published_at",
          order: DESC,
          limit: 3,
          filter: {
            typeName: { eq: BlogPost }
          }) {
          edges {
            node {
              ... on BlogPost {
                title
                path
                image(width: 640, quality: 75)
              }
            }
          }
        }
      }
    }
  }
</page-query>

Note that since now tags have references to both BlogPosts and Services, we need to filter by typeName so we only get posts.

That easily, every time we add a post about Vue, it will show up on the Vue service page. And it works the other way around too! We can query related services from the post page, as you can see at the end of this post. And we did the same with case studies! If you read a post about Decidim, you will see a link to the case study. Post tags enabled us to display relationships throughout the site.

Imagine the possibilities

As we've seen, the JAMStack is not limited to simple sites with a few pages, or blogs at the most. We can create complex relationships and filters very easily, so we can sort our products in multiple ways (categories, families, tags, etc), add related products, link them to a different content type… you name it! In this post, we've been working with a file loader but Gridsome supports multiple sources: you can use popular headless CMS like Contentful or Netlify CMS, Wordpress, or even plugging it directly to a MySQL database. So even if your site has a lot of content, it's easily manageable with JAMStack too.

Apparently, you can even use the Star Wars API as a source. So try Gridsome today, it is the framework you are looking for.