The Problem
Pantone's content pipeline was held together with duct tape. Articles, promotional banners, and campaign content lived in Contentful, but the integration with the BigCommerce storefront was a pair of vanilla JavaScript classes — PantoneContentfulArticles and PantoneContentfulArticle — that were dynamically injected into the page at runtime.
The scripts used setTimeout delays and MutationObserver to detect when the page was ready, then fetched from the Contentful API and rendered content into placeholder DOM nodes. The result was a fragile system full of race conditions: sometimes the content loaded, sometimes it spun forever. The observer would fire before the scripts had finished downloading, or the DOM elements would already exist by the time the observer started watching. Every locale required its own hardcoded URL check to determine whether the page was valid for injection.
This powered three pieces of the site:
The global pencil banner — the thin promotional strip across the top of every page ("Free shipping with code FREESHIP"), pulled from Contentful and localized per region.
The promotions page — a dedicated landing page showing current deals, coupon codes, and campaign details, all managed by the marketing team in Contentful.
107 articles — the full editorial content library at pantone.com/articles, with individual article pages and a collection landing page. Articles include rich text, embedded images, and cross-links to Pantone products and color pages.
All of this served across 10 multilingual sites spanning 4 regions and 7 locales.
What I Built
A complete rewrite as Next.js BigCommerce Catalyst server components. No more script injection, no more MutationObservers, no more race conditions.
Server-side data fetching. Contentful data is fetched at request time (or build time with ISR) on the server. No client-side API calls, no loading spinners, no timing dependencies. The content is in the HTML before it reaches the browser.
Typed Contentful models. Every content type — article, promotion, banner — has a TypeScript interface generated from the Contentful schema. Field access is type-checked at build time. No more guessing whether a field exists or what shape the rich text response takes.
Rich text rendering. Contentful's rich text responses are rendered server-side using a custom renderer that maps Contentful node types to React components. Embedded images resolve to Next.js \<Image> components with proper sizing and lazy loading. Embedded entries (product references, color swatches) resolve to the appropriate Pantone components.
Locale-aware routing. Articles and promotions resolve to the correct locale automatically through the existing next-intl routing infrastructure. No more hardcoded URL arrays to determine which locale is active — the locale comes from the route parameter.
Global banner component. The pencil banner fetches its content from Contentful based on the current locale and renders server-side. It's a single component in the layout — no injection, no observer, no race.
Technical Complexity
From imperative to declarative. The old system was a state machine: wait for DOM → check if scripts loaded → check if constructor exists → initialize → fetch → render. Every step could fail independently. The new system is declarative: fetch data, render component. React and Next.js handle the lifecycle.
Rich text edge cases. Contentful's rich text format can contain nested embedded entries, inline hyperlinks to other content types, and asset references. Each needs to resolve correctly in the context of a multilingual site — an embedded product link needs to point to the correct locale's product page, not the default.
107 articles at scale. The article landing page needs to display the full collection with pagination, category filtering, and locale-specific content. Individual article pages use dynamic routes with ISR so new articles published in Contentful appear without a full redeploy.
Promotional content scheduling. Banners and promotions have start/end dates managed in Contentful. The server component checks these dates at render time, showing or hiding content based on the current campaign schedule.
Results
The content system went from unreliable (intermittent loading failures, race condition bugs, console errors on every page load) to invisible (it just works). Marketing can publish articles, update banners, and schedule promotions in Contentful with confidence that the content will appear correctly across all 10 sites.
The migration eliminated an entire class of bugs — every timing-related issue, every "sometimes it loads, sometimes it doesn't" report — by moving data fetching to the server where it belongs.
Tech Stack
Next.js 15, TypeScript, Contentful CMS, BigCommerce Catalyst, React Server Components, next-intl, Rich Text Rendering, ISR
