Using Gridsome with Craft CMS

Judd Lyon • published a month ago • updated: 5 days ago

Gridsome is a Gatsby.js clone for those of us who prefer Vue.js to React. The driving idea behind these JAMstack frameworks is to build performant websites that pull data at build time. These JS apps output good old HTML/CSS/JS that you upload to your favorite CDN and voilà - the security and speed of a static website generated from a dynamic data source.

With Craft CMS announcing Craft Cloud and opting to follow the headless trend by exposing a GraphQL API, I figured I better explore how this stuff works. So I built the site you're reading with Gridsome and Craft.

"JAMstack" hearkens back to when Perl scripts ruled the web by spitting out HTML

Gridsome in a nutshell

Gridsome is a Vue.js application that leverages popular JavaScript packages like Webpack, Vue Meta, Vue Router, GraphQL, and Express. The basic workflow goes like this:

  1. Run a local Webpack server via the gridsome develop command
  2. Configure Gridsome to source data from somewhere (e.g. filesystem, REST API, GraphQL)
  3. Gridsome creates its own GraphQL data layer from your data source - this is the key thing to understand of kind of weird at first
  4. You query Gridsome's GraphQL API in your Vue single file components and Gridsome creates Pages and Routes based on folder naming conventions
  5. When you're happy with your work, you run gridsome build and it spits out a hyper-optimized static site
  6. You upload the site anywhere that can serve HTML/CSS/JS. Netlify, Zeit, and S3 are popular choices.
  7. When your data source changes you need to trigger a new build
My understanding of how Gridsome works

What are the advantages of using Gridsome?

If you're like me you're probably asking yourself "why would I go to all that trouble?" Good question. Here's a few reasons:

  1. Decoupling your API from your UI: this pattern allows you to consume your data source from multiple clients. For bigger companies this might be an intranet or a native mobile app. It also makes redesigns easier as your templates are more disposable by separating concerns.
  2. Performance: you're effectively running a local Node application as a high-tech website compiler. This gives you access to the vast NPM ecosystem, including all the Vue component libraries out there. Image optimization, code splitting, prefetching/prerendering and much more are all baked in to your build step. Gridsome follows the Push Render Pre-cache Lazy-load pattern.
  3. Data-stitching: the flexibility of GraphQL enables Gridsome to ingest disparate data sources and make them available to your Vue components in a somewhat seamless way. A popular use case is to have a headless e-commerce system responsible for the shopping cart and a separate content management system to power the marketing pages.

The downsides

  1. Complexity: more moving pieces means more things can break. Troubleshooting Webpack bugs or trying to figure out why your development site works and the built version bombs out can be frustrating. You also need a place to host the site as well as your data source if it's web-facing. Netlify and other JAMstack hosts make this easy, but it's still additional work.
  2. Building and re-building: your Gridsome site does not automatically update when your data source changes. It's possible to fire AJAX calls on new page loads, but that's venturing into Single Page App (SPA) territory and can have SEO consequences. Previewing changes can be extremely tricky. Gatsby is venture capital funded and their entire business model is running servers to speed up build times.
  3. Overthinking the problem: in my estimation the performance benefits of building sites this way are oversold. Server side websites have solved this problem a long time ago by caching dynamic content as HTML and instructing the webserver to bypass requests to the database and instead serving the static version. There's nothing keeping you from putting the static content behind a CDN either. Furthermore, template languages like Twig, Liquid, and Jinja 2 give you far more logical power and expressiveness than Vue or React. If you think I'm exaggerating then I'd invite you to look at elaborate workarounds Gridsome and Gatsby have to handle external images. As for tying together different systems, a reverse proxy like Nginx or AWS API Gateway are easier and better understood.
  4. Newb-hostile: if you're working on a team then you need to be sensitive to the varying abilities of each person. It's a lot to ask someone to understand NPM, Webpack, Vue, GraphQL, AJAX, CDNs, etc. WordPress is a phenomenon not because it's elegant but because you can point-and-click your way to a good website on cheapo hosting without a PhD in computer science.

My intention is not to be negative, I'm a fan of Gridsome but feel like the use-case for this type of tool is narrower than how it's being sold in JS-land.

Getting Craft CMS working with Gridsome

Teeing up Craft

Pro Tip! When you make additions to Craft (Fields, Sections, Asset Volumes, etc.) remember to enable them by selecting the checkboxes in your Schema. If you don't you'll get query errors that the Element doesn't exist.

Installing Gridsome

Follow these Getting Started instructions to get a vanilla Gridsome site going.

There are two primary ways to integrate with Craft:

  1. The official GraphQL source plugin from Gridsome (watch out, there's a forked version of the same name when you search)
  2. Mr. Ben Sheedy's work-in-progress source plugin Craft GraphQL. (He's also working on an ElementAPI source plugin if REST is more your thing.)

Don't get confused when you see the CraftQL source plugin from @JakeDohm. That's for Mark Hout's CraftQL plugin that predates the built-in support. They were way ahead of the curve on this stuff.

For this tutorial I'm using the official generic GraphQL plugin from Gridsome since I started this prior to Ben publishing his. I'd use his if I started today, he does a lot of heavy lifting for you and is very active in both the Craft CMS and Gridsome Discord channels.

Enough jibber-jabber, let's connect Craft!

First set your API URL and Token in a Gridsome dot env file. Then add the GraphQL plugin config.

/.env
CRAFT_API_URL=<YOUR API URL>
CRAFT_API_TOKEN=<YOUR API TOKEN>
/gridsome.config.js
{
  use: 'gridsome-source-graphql',
  options: {
    url: process.env.CRAFT_API_URL,
    token: process.env.CRAFT_API_TOKEN,
    fieldName: 'craft',
    typeName: 'craft',
    headers: {
      Authorization: `Bearer ${process.env.CRAFT_API_TOKEN}`
    }
  }

Use Gridsome's Server and Pages APIs

This is the trickiest part. Gridsome has a Server API which is the heart of its data sourcing functionality. Definitely spend time reading how this works and browsing some of the sourcing plugins. We're using GraphQL, but you can read from the filesystem, Markdown, JSON, as well as other data sources.

Under the hood the GraphQL source plugin uses Apollo Link Context, Apollo Link HTTP and Node Fetch.

We are going to use our Server Config to grab data from Craft. Then we're going to dynamically create pages from said data using Pages API.

Gridsome's nomenclature is a bit intimidating but accurate. It makes more sense when you start coding.

Here's my entire server config for this blog.

gridsome.server.js
module.exports = function (api) {

  api.createPages(async ({ graphql, createPage }) => {

    const { data } = await graphql(`{
      entries {
        id
        slug
        uri
      }
      categories {
        id
        slug
        uri
      }
    }`)

    await Promise.all(data.entries.map(async ({ id, slug, uri }) => {
      createPage({
        path: `/${slug}`,
        component: './src/templates/Blog.vue',
        context: {
          id: Number(id),
          slug: slug,
          uri: uri
        }
      })
    }))

    await Promise.all(data.categories.map(async ({ id, slug, uri }) => {
      createPage({
        path: `/blog/${slug}`,
        component: './src/templates/Category.vue',
        context: {
          id: Number(id),
          slug: slug,
          uri: uri
        }
      })
    }))

  }) // api.createPages
} // module.exports

Pro Tip! Backticks (`) are important in GraphQL, don't jack with them or try to turn them into single quotes.

Blog and Category Templates

Now we're to the fun part. We've plugged Gridsome into Craft CMS and mapped the data to our templates, which are Vue components. We can query whatever we need within each component.

From Craft to Gridsome to Vue

Blog.vue

Here's a trimmed down version of my Blog.vue component. The result of your Page Query is available in the $page object.

In my case contentBuilder is a "page builder" Matrix Field in Craft. Shout out to Chris Kennedy - I figured out how to query a Matrix field based on an example he posted to GitHub.

<template>
  <Layout>

    <h1>{{ $page.entry.title }}</h1>

    <section v-for="(block, index) in $page.entry.contentBuilder" :key="index">
          
      <template v-if="block.typeHandle == 'Heading'">
        <h2 v-if="block.h2Heading">{{ block.h2Heading }}</h2>
      </template>

      <template v-if="block.typeHandle == 'Text'">
        <h3 v-if="block.heading">{{ block.heading }}</h3>
        <div v-html="block.text"></div>
      </template>

      <template v-if="block.typeHandle == 'Image'">
        <figure>
          <img :src="block.image[0].url">
          <figcaption>{{ block.caption }}</figcaption>
        </figure>
      </template>

    </section>

  </Layout>
</template>

<page-query>
  query Entry ($slug: [String], $uri: String) {
    entry (slug: $slug) {
      title
      dateCreated
      dateUpdated
      ... on craft_blog_blog_Entry {
        overview
        contentBuilder {
          ... on craft_contentBuilder_Heading_BlockType {
            typeHandle
            h2Heading
            h3Heading
          }
          ... on craft_contentBuilder_Text_BlockType {
            typeHandle
            text
            heading
          }
          ... on craft_contentBuilder_Image_BlockType {
            typeHandle
            image {
              url
            }
            caption
          }
        }
      }
    }
    seomatic (uri: $uri, asArray: true) {
      metaTitleContainer
      metaTagContainer
      metaLinkContainer
      metaScriptContainer
      metaJsonLdContainer
    }
  }
</page-query>

<script>
  import seomaticMeta from '../mixins/seomaticMeta';
  export default {
    mixins: [seomaticMeta],
    name: 'BlogEntry',
    metaInfo() {
      const seo = this.seomaticMeta(this.$page.seomatic);
      seo.bodyAttrs = {
        class: 'nav-light',
        id: 'blog-entry'
      }
      return seo;
    }
  }
</script>

<style>
  /* component-specific CSS */
</style>

Challenges

  • Images: Gridsome uses a custom component called <g-image> to optimize images. Externally hosted can't be processed, which is unfortunate because your GraphQL is almost certain to contain external images. Note that this is a challenge for Gatsby as well an not a Gridsome-specific problem.
  • Metadata: with a traditional Craft site you can run SEOmatic in magic mode where everything is injected into your pages. With Gridsome you need to query this data and figure out how to render it. Gridsome uses Vue Meta from Nuxt so I adapted Ben Rogerson's Nuxt SEOmatic Meta plugin. It's beyond the scope of this article, but here's the SEOmatic Mixin (Gist) referenced in my example.
  • Pagination: instead of using Craft's built-in Twig tags you need to use Gridsome's implementation, which is more complex.

Conclusion

Gridsome shows a ton of promise and is a great choice if you're experienced with Vue. It's clear with the pending launch of Craft Cloud that Pixel & Tonic is investing heavily in headless and GraphQL it makes sense to explore building Craft sites JAMstack-style. Hopefully Craft will provide official integration guidance with popular frameworks (Next, Nuxt, Gatsby, Gridsome, et al.). Until then I'd reserve this way of building sites for small client sites and side projects.