Using GraphQL and Apollo to create highly dynamic pages in Nuxt/Vue


by Luke Miller

First off, I'm a frontend developer. So database queries and terms like "Dynamic Fragment Matching" usually make me queasy. Initially, I was pretty intimidated by GraphQL but with the help of the Apollo GraphQL Client it turns out to be pretty easy to pull data into your frontend views. Recently we ported a moderately large website from a legacy custom built CMS to the very cool and flexible "Headless" CMS platform DatoCMS with a new frontend built with Nuxt/Vue.

For the proposes of this post, I'll be just be covering GraphQL queries using the Nuxt Apollo module - which allows us to use the Vue Apollo Client inside a Nuxt Application and how we'll use that to grab some variable content from Dato's GraphQL endpoint.

As a side note, we have built a few projects with Nuxt and have found it to be a very flexible and easy to use tool. More on that in a future post.


Dato provides an excellent GraphQL explorer to help plebs like me to figure out my query structure. Right off the bat, I noticed one of the really nice things about GraphQL: all the data you need for the pages can be fetched in one request even if you need to query several different models. And on top of that, grabbing whatever attributes from the models you are querying is really intuitive.

Let's start with a simple query...

Note: all our queries are written using gql and are in a separate queries.js file. You need to include gql in your queries file like so:

import gql from 'graphql-tag'

And each query can be imported into your .vue files like so

import { myQueryName } from '~/apollo/queries.js'. // or wherever you keep yours

The query:

export const simpleQuery = gql`{
    allPosts {
        id
        slug
        title
    author {
        name
        avatar {
        url
        }
    }
    }
}`

Nice and clear and we know what to expect for a response.


In the real world though, we need a whole bunch of different stuff and things are never simple.


As part of our porting of the old CMS to Dato, we needed to preserve most of the existing posts, and thus most of the existing post data structure. In Dato, using their very useful "Modular Content Blocks" I created a set of Modular Blocks that could replicate most of the structure and functionality of the old CMS - thus enabling an export from the old system into Dato. The cool thing about Modular Content Blocks is that you can use some, all or none of the block types in your content block. This is really handy for editors to be able to create posts with a variety of content layouts to choose from. On the query side, each modular content type is defined as a GraphQL Fragment to make them re-usable across different queries.

Things get interesting when you try to query these things from Apollo as the Apollo Client doesn't know the GraphQL schema -- GASP Dynamic Fragment Matching!!!

Here's our "real world" query:

export const postContentQuery = gql`query post($slug: String) {
    post(filter: {slug: {eq: $slug}}) {
        title
        id
        postContent {
            ... on HeroModueRecord {
                ...heroModuleFragment
            }
            ... on TextAndMediaModuleRecord {
                ...textAndMediaModuleFragment
            }
            ... on TextModuleRecord {
                ...textModuleFragment
            }
            ... on TitleOnlyModuleRecord {
                ...titleOnlyModuleFragment
            }
            ... on MediaOnlyModuleRecord {
                ...mediaOnlyModuleFragment
            }
        }
    }
}
${ responsiveImageFragment }
${ heroModuleFragment }
${ textAndMediaModuleFragment }
${ textModuleFragment }
${ mediaOnlyModuleFragment }
${ titleOnlyModuleFragment }`

However, this throws a bunch of "Heuristic Fragment Matching" errors

WARNING: heuristic fragment matching going on!

Gaahhh! Run away! As a frontend dev, this stuff sucks. To solve this problem, we need to tell the Apollo client about the GraphQL schema so it can figure things out. Enter the "IntrospectionFragmentMatcher". Also, Apollo has a cache where it can store this stuff, so we just need to tell the FramentMatcher about our Schema and then put it in the cache. Easy peasy.

First you need your schema which you can get by running this query (in Dato, you can use the API Explorer):

query Schema {
  __schema {
    types {
      name
      kind
      possibleTypes {
        name
      }
    }
  }
}

Save that out as a json file in your /apollo directory.

If you don't already have your Apollo config as a separate file, you should do that. Then you can tell Nuxt about it in your nuxt.config.js file like this:

/* Apollo Options */
apollo: {
  clientConfigs: {
    default: '~/apollo/config.js'
  }
},

/apollo/config.js

import schema from './dato_schema.json'
import { InMemoryCache, IntrospectionFragmentMatcher } from 'apollo-cache-inmemory'

const fragmentMatcher = new IntrospectionFragmentMatcher({
  introspectionQueryResultData: schema.data
})

const cache = new InMemoryCache({ fragmentMatcher })

export default function(context) {
  return {
    cache,
    // a bunch of other options here that aren't applicable
  }
}

What's great about all this?

In short, content editors have a fairly robust set of layouts (each of those modular content blocks has a bunch of options for layout, styles, colors, etc) and our Vue components are set up to handle a dynamic set of possible query results. And all I had to get over was my fear of Fragments and database schemas.