Get your copy[New whitepaper] Why E-commerce Customer Experiences Matter (And How to Improve Them)
Petil shape

How to avoid production errors caused by content changes on Jamstack sites

Some pages of your website on production return error 500. You don’t know why. There have been no recent code changes, so it must be connected with a content update.


Ondrej PolesnyDec 20, 2021

In this article, I’ll explain how content changes can break your website and how CI can actually help you avoid outages caused by these failures. In the scope of this article, I’ll focus on JavaScript-based websites, however, the principles can be applied to any language.

Restricting the implementation

We, developers, like to be restricted. When we implement a web application, we prefer type-safe languages so that we discover potential issues as soon as possible—either while coding or at build time. We add unit and integration tests to make sure our changes don’t have side effects and that everything stays working.

But every website has two components: the code and the content. The measures I mentioned above give us confidence in the code part, but what about content?

Sure, we have a granularly defined content model, we’ve carefully prepared every content type, limited the usage of content items in Linked items and Rich text elements, defined roles in a way that disables unauthorized people to change content types, but still—is that enough? Does it also protect us from ourselves?

Audit log in Kontent by Kentico

This is a screenshot of the Audit log showing the latest changes to the Kontent.ai website content model. As you can see, model changes are not that uncommon, especially on large websites. In our case, they were all performed by a single person—the lead developer on the project—and in a separate environment. It does, however, demonstrate that there exists a risk in relying on a single person, or worse, multiple developers, to keep the implementation up to date with all these changes.

Adding strongly-typed content and structure

What is the worst that can happen? If you’re thinking about a broken build, that’s the least of a problem. If the build fails, you know something is wrong. It’s much worse if some pages start returning error 500, show other errors, or even behave normally with missing content. The runtime problems are the worst since they’re hard to find.

So how do we solve the situation?

Using generated codenames

First, we need to mitigate the errors introduced by typos and codename changes. The new JavaScript Delivery SDK brought major updates to the model generator, which now allows us to generate project structure including all codenames. Previously, a typical content query for blog posts looked like this:

const blogPosts = await deliveryClient
  .items<BlogPost>()
  .type("blog_post")
  .orderByDescending("elements.date")
  .limit(3)
  .toPromise()

In the new version 11, when using the generated project structure, it looks like this:

const blogPosts = await deliveryClient
  .items<BlogPost>()
  .type(projectModel.contentTypes.blogPost.codename)
  .orderByDescending(`elements.${projectModel.contentTypes.blogPost.elements.date.codename}`)
  .limit(3)
  .toPromise()

This solves both the typos and the codename changes.

Keeping generated types up to date

Moving forward on the content gathering query, the returned items are mapped to the BlogPost type. All pages and components that use it can benefit from type safety as long as the generated type is up to date with its corresponding content type BlogPost in the headless CMS.

We achieve that by regenerating the types before every production build.

The headless CMS is the source of truth for the content model and the generated types. Not the site implementation. However, we, developers, like to keep track of all the changes, and we want to have the generated types committed in source control.

Therefore, to discover type mismatches, we’ll incorporate a check into the production build pipeline. We’ll generate a fresh set of types into a temp folder and perform a simple directory diff against the types we have in our implementation.

First, we install the dir-compare module that will take care of the comparison:

npm i dir-compare --save-dev

Then, we’ll add a Node script verify-types.js that will:

  • Generate the types from the current content model in Kontent
  • Compare them with our tracked types
  • Stop the build in case there are any differences
const { mkdirSync, rmdirSync } = require('fs');
const { generateModelsAsync } = require('@kentico/kontent-model-generator');
const dircompare = require('dir-compare');
require('dotenv').config();

async function verifyModels(existingTypesFolder){
    console.log('Verifying content model...');

    const date = new Date();
    const tempFolderName = `tmpModels${date.getFullYear()}${date.getMonth()}${date.getDate()}${date.getHours()}${date.getMinutes()}${date.getSeconds()}`;

    // create temp folder and generate types
    mkdirSync(tempFolderName);
    process.chdir(tempFolderName);
    await generateModelsAsync({
        sdkType: 'delivery',
        projectId: process.env.KONTENT_PROJECT_ID,
        addTimestamp: false,
    });
    process.chdir('..');

    // compare generated types with existing types
    const diff = await dircompare.compare(existingTypesFolder, tempFolderName, {
        compareContent: true,
    });

    rmdirSync(tempFolderName, { recursive: true });

    if (diff.same){
        console.log('Content model is unchanged.');
        return;
    }

    console.error(`There are ${diff.differences} differences:`);

    diff
        .diffSet
        .filter(i => i.state !== 'equal')
        .forEach(d => console.error(d));

    console.error(`Content model contains unexpected changes. Regenerate the types in your project.`);
    process.exit(1);
}

const existingTypesFolder = process.argv[2];
verifyModels(existingTypesFolder);

Note: If you adjusted the configuration of the model generator in your project, make sure to configure it the same way in the script too.

The script expects a single parameter—the path to the directory of your generated types:

node ./verify-types.js {directory of your generated types}

Make sure the script is at the root level of your project and adjust the production build script in package.json:

{
  ...
  "scripts": {
    ...
    "build": "node ./verify-types.js {directory of your generate types} && next build",
  }
}

Note: My project is based on Next.js, your build script may be different.

Remember, you only want to verify models on a production build. On local, it’s fine to do it manually, as the content model changes are typically less frequent than you running npm run dev.

Failing build = issue avoided

Now, when there’s a content change and you need to do a full or a partial rebuild of production, the build server will first generate the types and compare them to those that you track in your implementation. If there’s a difference, the build will fail with an error showing you exactly what’s different:

Build error

Note: In this case, the content type Block with image was changed.

Your production site remains untouched until you resolve the issue.

Note: If you use the project structure constants in your code, also codename changes will stop the build even though they are harmless. I personally like to know about these changes too, but feel free to update the verify-models.js script to ignore them.

Conclusion

In this article, I showed you how a content model change can be dangerous to your production website and suggested checking the generated models and project structure of your content with the headless CMS during every production build. This step adds safety and helps you avoid unexpected issues where it could hurt you the most.

Find the links to the mentioned resources below and make sure to join our community on Discord if you need help or want to discuss your project specifics.

Resources:

Written by

Ondrej Polesny

As Developer Evangelist, I specialize in the impossible. When something is too complicated, requires too much effort, or is just simply not possible, just give me a few days and we will see :).

More articles from Ondrej