How do you integrate a headless CMS with fast builds?
JAMstack Developer
answer
I integrate a headless CMS by separating preview and publish paths. Drafts use a secure preview mode with short-lived tokens, edge caching, and client-side hydration. Published content uses SSG plus ISR or on-demand revalidation to keep builds fast. I avoid over-fetching with field-level GraphQL queries, persisted queries, and pagination. Webhooks batch invalidations, trigger selective rebuilds, and update a dependency graph so only affected routes regenerate. Everything is observably cached and measurable.
Long Answer
A robust JAMstack integration with a headless CMS balances authoring ergonomics, security, and runtime speed. My approach is to cleanly separate read paths (preview versus publish), reduce fetch cost with strict query hygiene, and use targeted regeneration so builds remain fast even as content grows.
1) Architecture overview: two lanes, one source of truth
I maintain two lanes:
- Preview lane for authenticated editors to see drafts and scheduled content.
- Publish lane for public users with immutable static assets, CDN caching, and Incremental Static Regeneration or equivalent.
Both lanes read from the same CMS (Contentful, Sanity, or similar), but use different tokens, cache policies, and rendering modes.
2) Preview mode and draft gating
Preview must be instant and safe.
- Secure entry: Editors enter preview via a signed link that sets an HTTP-only cookie bound to a short-lived JWT and a CMS preview token. I restrict origins, log user and space IDs, and expire tokens quickly.
- Draft gating: The app switches data sources when the preview cookie is present, querying the CMS with preview=true (Contentful) or using Sanity’s drafts. overlay. Components render the draft overlay and label content state clearly.
- Rendering: I prefer server-side rendering at the edge or on-demand ISR for preview, with cache bypass, so editors see changes within seconds without triggering a full build.
- Safety: Preview pages disable indexing, add a visible banner, and return X-Robots-Tag: noindex to protect unpublished material.
3) Fast builds with selective regeneration
Full rebuilds do not scale with large content libraries. I keep builds fast by:
- ISR / On-demand ISR: Statically pre-generate only the top N routes (home, hubs, recent posts). All other routes use fallback and regenerate on first request, then serve from the cache.
- Content dependency graph: At build time, I record which routes depend on which CMS entries and assets. Webhooks carry entry IDs; the revalidator invalidates only the affected pages.
- Batching: Webhooks debounce by type and space. Multiple edits within a short window result in one regeneration batch.
- Background regeneration: Heavy pages regenerate in the background and keep serving the last good version until the new one is ready, preserving availability.
4) Avoid over-fetching: query hygiene and transport controls
CMS APIs are chatty by default. I control them with:
- Field-level GraphQL selections and type fragments that match view models. No wildcard fields, no kitchen sink queries.
- Persisted queries stored server side or at the CDN. Clients send a hash, not a large query. This reduces payload, improves caching, and blocks ad hoc query sprawl.
- Pagination and slicing: I pull only the first page needed for the route and lazy load the rest client side. Index pages fetch IDs first, detail pages fetch full records.
- Conditional references: For deep reference trees, I request only IDs during list builds, then hydrate details on demand to cut build time.
- ETag and If-None-Match: Server fetchers honor conditional requests so unchanged entries do not count against rate limits.
5) Caching strategy: layered and observable
- Edge cache: Statically generated HTML and assets sit at a CDN with long TTL plus stale-while-revalidate. ISR pins the freshness window.
- Application cache: Server fetchers use an LRU or Redis keyed by {space}:{env}:{entry}:{fields-hash}, with short TTLs in preview and longer TTLs in publish.
- Image pipeline: I serve images through an optimizer (width, format, quality) with immutable URLs, so transformed assets are perfectly cacheable.
- Cache busting: Content hashes version JSON payloads. A changed hash invalidates the right objects automatically.
6) Webhooks and editorial workflow
- Source events: Entry publish, unpublish, delete, and asset replace trigger webhooks. The payload includes entry IDs, locales, and content types.
- Router: A small webhook service authenticates calls, normalizes payloads, consults the dependency graph, and enqueues targeted revalidation jobs.
- Protection: Idempotency keys prevent duplicate invalidations; rate limits and HMAC signatures block spoofed calls.
- Scheduling: For scheduled publishes, a cron or CMS schedule API triggers revalidation at the moment content goes live.
7) Locales, A/B tests, and personalization
- Locales: I shard regeneration by locale, so a French edit does not rebuild English pages.
- Experiments: Experiments resolve server side using a stable assignment key; I avoid branching the build per variant. Payloads include experiment metadata so ISR can retain cache segmentation safely.
- Personalization: Static shells plus client-side data fetch keep personalization fast without exploding the number of static variants.
8) DX, governance, and guardrails
- Schema as code: I pin CMS schemas in version control and validate queries against them during CI with codegen.
- Performance budgets: CI fails if a query adds unapproved fields, if a page depends on too many entries, or if a route’s build time exceeds budget.
- Observability: Dashboards show webhook volume, queue age, ISR revalidation latency, CMS rate limit usage, cache hit ratio, and preview latency. Alarms trigger when preview drifts beyond a few seconds or when revalidation backlog grows.
The result is a headless CMS integration that gives editors real-time preview and reliable scheduling, while production remains fast through ISR, targeted revalidation, strict query hygiene, and observable caching.
Table
Common Mistakes
- Building every route on every publish, causing build times to balloon.
- Using wildcard GraphQL selections that pull unnecessary fields and nested references.
- Treating preview like production and caching drafts, leaking private content.
- Invalidating the entire site on any webhook rather than regenerating specific routes.
- Rendering all locales and experiments statically, exploding the page count.
- Skipping content hashing, leading to stale CDN objects.
- Ignoring CMS rate limits and ETag support, causing throttling during spikes.
- No dashboards for ISR latency, queue depth, or preview error rates.
Sample Answers
Junior:
“I enable a preview mode that uses a secure cookie and a CMS preview token. I use ISR so only popular pages build at deploy time. I write field-level GraphQL queries to avoid extra data, and I trigger revalidation from webhooks when an entry is published.”
Mid:
“I store a dependency map of entry to routes. Webhooks authenticate with HMAC, batch updates, and enqueue targeted on-demand revalidation jobs. I use persisted queries and pagination to prevent over-fetching, and I cache fetches in Redis with short TTLs for preview and longer TTLs for publish.”
Senior:
“My pipeline separates preview and publish, enforces draft gating, and validates queries against a checked-in schema. ISR plus a dependency graph keeps builds fast. Webhooks are idempotent and selective. Edge caching uses SWR; image URLs are hashed and immutable. CI enforces query budgets and route build time budgets, and dashboards track ISR latency, rate limits, and cache hit ratio.”
Evaluation Criteria
A strong answer shows a two-lane architecture (preview versus publish), secure draft gating, and ISR or on-demand revalidation to keep builds fast. It prevents over-fetching with field-level selections, persisted queries, and pagination. It uses webhooks to drive selective invalidations based on a route-to-entry graph. It layers CDN, application, and image caching, and it proves reliability with rate limit handling, idempotency, and dashboards. Red flags include full-site rebuilds, wildcard queries, caching preview responses publicly, and unscoped invalidations.
Preparation Tips
- Implement preview mode with a signed cookie and a short-lived CMS token; add a visible draft banner and noindex.
- Add ISR and configure on-demand revalidation; prebuild only top routes.
- Build a dependency graph that maps entry IDs to routes; persist it during CI.
- Convert one large query into a persisted query with tight field selections and pagination.
- Add a webhook router that verifies HMAC, batches events, and enqueues targeted revalidations.
- Introduce content hashing for JSON and image URLs.
- Create dashboards for ISR latency, cache hit ratio, rate limit usage, and preview error rate.
- Set CI budgets that fail on over-fetching or slow route generation.
Real-world Context
A media site moved to ISR and persisted queries; build time fell from forty minutes to five, while first hit latency remained low due to fallback rendering. An e-commerce blog added a dependency graph and batched webhooks; publishing a post regenerated only three pages instead of the entire catalog. A global brand separated preview with draft gating and edge SSR; editors saw changes in under two seconds without public caching risk. A documentation portal hashed JSON payloads and images; CDN invalidations became automatic and precise, with cache hit ratio climbing above ninety percent.
Key Takeaways
- Separate preview and publish lanes with secure draft gating.
- Keep builds fast with ISR and targeted revalidation driven by a dependency graph.
- Prevent over-fetching using field-level GraphQL, persisted queries, and pagination.
- Layer caching at the CDN, application, and image pipeline with content hashes.
- Batch and authenticate webhooks; monitor ISR latency, queue depth, and rate limits.
Practice Exercise
Scenario:
You are integrating Contentful with a Next.js site that has ten thousand articles. Editors require instant preview of drafts. Publishing must not trigger a full rebuild. You also need to eliminate over-fetching in list pages.
Tasks:
- Implement preview mode using a signed link endpoint that sets an HTTP-only cookie and a short-lived preview token. Render with edge SSR and add a draft banner and noindex.
- Enable ISR with a small prebuild set; configure fallback for the long tail.
- Build and persist a dependency graph mapping article IDs and category IDs to their routes.
- Replace wildcard GraphQL queries with persisted queries that select only fields needed per route. Paginate lists and fetch IDs first, then hydrate details per article page.
- Create a webhook router that verifies HMAC, batches events for one minute, and enqueues on-demand revalidation for affected routes only.
- Add content hashes to JSON and image URLs; serve images through an optimizer with immutable URLs.
- Add dashboards for ISR latency, queue depth, rate limit usage, and cache hit ratio; set alerts for preview exceeding two seconds or revalidation backlog growth.
- Run a load test that simulates heavy publish bursts and cold hits; record build time, first hit latency, and cache hit ratio.
Deliverable:
A working integration plan that demonstrates secure preview, precise draft gating, batched webhooks, strict query hygiene, fast builds via ISR, and measurable performance at scale without over-fetching.

