DEV Community

John Mueller
John Mueller

Posted on • Edited on

3

Using Craft 3 as a headless CMS with GraphQL and Vue Apollo

Using Craft 3 as a headless CMS with GraphQL and Vue Apollo
As a member of the brilliant team at Telegraph, I recently had the opportunity to use Craft in a new way and thought others might find it helpful to read about our process. Our team was tasked with adding functionality to the Neighborhood Goods website to detect a visitor’s location and offer content and products based on the nearest Neighborhood Goods store.

The Setup

Neighborhood Goods relies on Shopify for online sales and inventory management. While we’ve found that Shopify is great at those tasks, it falls short when trying to manage editorial content. Basic blog entries work fine, but when you start to add more complex layouts, events, and location specific content, Shopify just didn’t seem up to the task.

Enter Craft CMS! We are big fans of Craft and while Twig can be handy for templating within Craft, our template files still needed to live within the realm of Shopify’s templates to take advantage of Shopify’s full feature set. This led us to use Craft 3 as a headless CMS which would provide all of our data via a GraphQL API. This was our first time working with the CraftQL plugin and it made setting up a GraphQL schema and managing granular authentication quite easy.

Once CraftQL was set up we turned to Vue (our front end Javascript framework of choice) and Vue Apollo to consume our API within Shopify templates. This made creating ad hoc queries easy for our front end development. For about 90% of our set up everything was ready to go out of the box. We did encounter a few gotchas, and I thought I’d share those here.

Things to Consider

One issue that we encountered with this setup is the way we handle Matrix Fields. Typically if we have a section of a page that could display different types of content we create a Matrix Field with several optional Block Types. For example: if there is a page that displays basic paragraph text, as well as optional hero images or hero text, we’d build out a Matrix Field that looks like this:

Dynamic Matrix Fields

Then, in Twig we can loop through each Block Type and include the proper template. This gives great flexibility for content where you may have one, many or no Blocks of a certain Type and they can be in any order.

    {% if blocks | length %}
        {% for block in blocks.all() %}
            {% switch block.type %}
                {# Article Body #}
                {% case "articleBody" %}
                    {% include '_components/longform-blocks/article-body' %}

                {# Text Hero #}
                {% case "textHero" %}
                    {% include '_components/longform-blocks/text-hero' %}

                {# Image Hero #}
                {% case "imageHero" %}
                    {% include '_components/longform-blocks/image-hero' %}
            {% endswitch %}
        {% endfor %}
    {% endif %}
Enter fullscreen mode Exit fullscreen mode

While testing this structure in CraftQL’s playground everything looked good:

CraftQL Playground

    query {
        entries(id: 3) {
            title
            id
            ...on MatrixExample {
                dynamicContent {
                    ... on DynamicContentBodyText {
                        copy
                    }
                    ... on DynamicContentTextHero {
                        text
                        backgroundColor {
                            hex
                        }
                    }
                    ... on DynamicContentImageHero {
                        image {
                            url
                        }
                        caption
                    }
                }
            }
        }
    }
Enter fullscreen mode Exit fullscreen mode

However, when trying to pull this query in via Vue Apollo, we encountered the following error:

Heuristic Fragment Warning

WARNING: heuristic fragment matching going on!
Enter fullscreen mode Exit fullscreen mode

It turns out that Vue Apollo can handle Matrix fields if there is only one Block Type (Body Text in the example above). If there are multiple Block Types (Body Text and Text Hero, etc.), Vue Apollo warns that it doesn’t know the full schema and can’t predict what type of content it is receiving. After some digging I found that we weren’t the only ones to encounter this issue.

Our workaround for this issue ended up being a combination of deciding the maximum number of sections and creating Text and Image Asset Fields to accommodate, and creating Matrix Fields with optional Fields that could be dual purpose (ie: a Hero Matrix that can accept either Text or an Image).

Matrix Field Workaround

Another option as described in the Github issue is to run a script on the command line (./craft craftql/tools/fetch-fragment-types) which generates a JSON file to include in your Vue Apollo setup. In hindsight, this seems like an easy enough task as long as everyone on your team remembers to run the command after editing Fields in Craft. I would like to try this approach on future projects. Have you used this approach? Do you have any advice on how to integrate it into your development/deployment workflow?


One other thing that wasn’t as straightforward as I had hoped with CraftQL is querying by a field value. When querying entries you can use the slug like so:

    query {
      entries(
        type: MatrixExample
        slug: "first-example"
      ) {
        id
        title
      }
    }
Enter fullscreen mode Exit fullscreen mode

I had hoped that we could do the same thing when querying by a Category Field that is attached to the Entry, but at this point in time you can only query that relationship using the Id:

    query {
      entries(
        type: MatrixExample
        relatedTo: [{element: 36}] # 36 is the Id of our Category
      ) {
        id
        title
      }
    }
Enter fullscreen mode Exit fullscreen mode

Going Forward

Craft 3.3 or later with a Pro license now includes GraphQL functionality. Based on a brief look it appears that the setup is a bit different than with CraftQL. I’m curious to see if there are any benefits in performance/functionality there. Have you used both? Any thoughts on how they compare?

Image of Timescale

Timescale – the developer's data platform for modern apps, built on PostgreSQL

Timescale Cloud is PostgreSQL optimized for speed, scale, and performance. Over 3 million IoT, AI, crypto, and dev tool apps are powered by Timescale. Try it free today! No credit card required.

Try free

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Discover a treasure trove of wisdom within this insightful piece, highly respected in the nurturing DEV Community enviroment. Developers, whether novice or expert, are encouraged to participate and add to our shared knowledge basin.

A simple "thank you" can illuminate someone's day. Express your appreciation in the comments section!

On DEV, sharing ideas smoothens our journey and strengthens our community ties. Learn something useful? Offering a quick thanks to the author is deeply appreciated.

Okay