When building or migrating to a Headless CMS, we’re on a mission to deliver a simple interface to empower marketers. That simplicity is shown as an interface that provides a simple authoring experience that matches how the business thinks. On the technical side, that simplicity is driven by an architecture that can scale as our solution expands to encompass multiple lines of business or even multiple brands.

We often lean on Contentful to help us establish clean authoring interfaces. And to enable marketers to publish content live, we also look toward Next.js. Next.js offers Incremental Static Rendering (ISR), which can help us quickly update content without needing to redeploy the entire application. Using these tools together, we can quickly build amazing applications, but the challenges grow as we try to scale out our usage.

A Tale of Two APIs

To build static pages in Next.js, we must first request all pages in our getStaticPaths function for a route. Then for each page, we get the URL/slug for one of those paths in getStaticProps to build the page that will be statically rendered. To do this efficiently, Next.js uses asynchronous workers for each page that only get the URL to form their request.

When we request from Contentful, we most frequently use the Content Delivery REST API using the Javascript SDK. This API returns the requested content and the tree of referenced content entries which allows us to construct a page based on the result. It's a large request, and we must paginate requests once we reach hundreds of content entries.

At a base level of integration, we make a large request to get all of our pages, return the URLs to getStaticPaths and then for each page we make a subsequent re-request filtered by the URL. But when we try to consider multiple page templates and multi-language support we make many more requests to build our page. At this point, we hit request limits, and our builds are slow due to the specific constraints of Next.js integrated with Contentful.

Adjusting for Contentful Request Limits

Fortunately for us, Contentful also offers a GraphQL API for us to use. In our CMS projects, we haven’t found a lot of good use cases for this API. We build dynamic pages off of the combination of many linked Content Types, and we struggle to define the increasingly complex types needed for a GraphQL request to return a result:

{
  pageCollection {
    items {
      slug
      contentCollection {
        ... on HeroBanner {
          # all hero banner fields
        }
        ... on ConentBanner {
          # all content banner fields
        }
        # repeat many more times
      }
    }
  }
}
GraphQL complexity limits here also further limit how many results we can get back from the API.

However, where the GraphQL API struggles to make a general request that represents the connections between our data, it does an amazing job at quickly requesting very specific data. We can use this to make fewer requests and keep us from hitting the API request limit as often:

{
	enPage: pageCollection (locale:"en-CA", slug:${pageName}) {
		total
	}
	frPage: pageCollection (locale:"fr-CA", slug:${pageName}) {
		total
	}
	enPromo: promoCollection (locale:"en-CA", slug:${pageName}) {
		total
	}
	frPromo: promoCollection (locale:"fr-CA", slug:${pageName}) {
		total
	}
}

Where previously we might need to make 3 requests to get a page between the two languages and two-page types, we can now make a single GraphQL request to determine the page type from the URL and then make a subsequent REST request for the page in question!

To go into more details on the REST API, we also have a constraint when requesting multiple content models. To be able to filter based on a field we must request a single specific Content Type. This means that we can make requests across multiple content types but cannot filter the results being requested until after we get the response. This leads to an increased request load.

Scaling the Authoring Experience

When looking at scaling it’s important to keep in mind who you’re building for. We need to make sacrifices to drive further expansion of our solutions while continuing to serve users with a good experience. When it comes to a CMS, that customer is the marketers and content authors.

We could work around these technical problems above by cluttering our page interfaces to handle more page types or by shifting to route certain types of pages under different paths. But we don’t want to clutter our interfaces, and building pages shouldn’t feel like filling out your taxes. Limiting pages under specific routes similarly limits our abilities for marketers to define URLs to meet their page design intents.

Quoting Martin Fowler, we want to “Abstract the capability, not the system.” We want to drive for these page creation capabilities while keeping our systems flexible and efficient. A big part of that is finding solutions to help lean into the strengths of our technology stack and find better interfaces for our integrations.