DEV Community

Telegraph Creative
Telegraph Creative

Posted on

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 %}

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
                    }
                }
            }
        }
    }

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!

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
      }
    }

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
      }
    }

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?

— Words by John Mueller

Top comments (1)

Collapse
 
daltonrooney profile image
Dalton

I ran into problems with the native GraphQL implementation in 3.3 and continued using CraftQL on my latest few projects. I'm not so happy about that decision because CraftQL clearly isn't getting updates from the developer any longer and I've found more than a few bugs or problem areas and I imagine will only get more broken as Craft continues to be updated. I've also hit a few snags with performance in CraftQL and ended up branching it to add my own caching logic.

I'm happy to say that the native GraphQL implementation has improved quite a bit in Craft 3.4. You can now query entries by field value, and combine multiple relatedTo queries more easily.

github.com/craftcms/cms/issues/5200
github.com/craftcms/cms/issues/5208

Combining multiple relatedTo queries was particularly challenging in CraftQL so this is a very welcome improvement. Not to mention built-in caching plus the support and ongoing development from the P&T team means it would be very hard to justify using CraftQL on future projects.