Static Generation
Production pre-renders every content page at build time and never re-renders it at runtime. Content changes go live through a rebuild that a Contentstack publish triggers.
Why
On Launch, every request that reaches the origin costs ~2.7 s or more, and there is no shared ISR cache. With 30-minute ISR, each page was rendered on its first visit and again after every expiry, and an expired page re-rendered in the background on every origin hit. Pre-rendering takes rendering off the visitor's path: pages come from the build output and carry a long CDN lifetime.
How it works
- Content routes export
revalidate = falseandgenerateStaticParams:app/[[...slug]],app/blog/[slug],app/guides/[...slug],app/kickstarts/[...slug],app/livestreams/[...slug]andapp/person/[name].- The markdown handlers under
app/md/*. For guides this includes{parent}/fullfor parent guides that have chapters.
lib/contentstack/staticParams.tslists every entry with aurlfor each content type:- It pages through results 100 at a time and fetches only
urlandchapter. - It doesn't filter on
include_in_sitemap, so noindex pages are pre-rendered too.
- It pages through results 100 at a time and fetches only
dynamicParamsstaystrue. An entry published after the last build still renders on its first request.- Unknown URLs return 404.
- Responses send
Cache-Control: s-maxage=31536000, so Cloudflare keeps them until the next deploy purges the cache. sitemap.xml,sitemap.md,llms.txt,agents.md,robots.txt,/.well-known/mcp,/api/openapi.jsonand/index.mdalso userevalidate = falseand refresh on each build./searchand/api/*stay dynamic.
Preview
- When
NEXT_PUBLIC_CONTENTSTACK_PREVIEW=true,staticParams.tsreturns no params, so nothing is pre-rendered. - Pages render on demand as the
Previewshell, which fetches content in the browser for Live Preview. next.config.tsaddsCache-Control: private, no-storeto every response in preview builds, so preview pages never sit in a CDN.
Don't call connection(), headers() or cookies() in these routes. Next returns a 500 when a route configured as static switches to dynamic at request time.
Rebuild on publish (setup)
Do this once per production environment:
- Launch: open the production environment, then Settings → Deploy Hooks. Create a hook and copy its URL.
- Contentstack: go to Settings → Webhooks → New webhook.
- URL: the deploy hook URL.
- Method: POST.
- Channels: entry published, entry unpublished, entry deleted and asset published, scoped to the production environment.
- Content types:
page,blogpost,guide,kickstart,live_stream,person,headerandfooter.
- Publish a test entry. A new deployment should start in Launch, and the change should appear once it finishes, which takes a few minutes.
Notes:
- Bulk publishes and releases send one webhook per item. Check how Launch handles a burst of deploy hook calls (whether it queues or collapses them). If it queues each one, put the webhook behind a short debounce, for example a Contentstack Automate flow with a delay.
- CDN purge: after a deploy, confirm the page's
x-deployment-uidheader changes and Cloudflare serves the new HTML. If Launch doesn't purge Cloudflare on deploy, call the Launch Revalidate CDN Cache API after the deploy finishes, or the year-longs-maxagewill keep serving old pages. - Cache priming: after each deploy,
launch.jsonwarms the top 50 URLs (see CachePriming).
Verify
npm run buildshould show●(SSG) for every route listed above, with noƒexcept/searchand/api/*.next start: a content page returnsx-nextjs-cache: HITandCache-Control: s-maxage=31536000without calling Contentstack. An unknown URL returns 404.NEXT_PUBLIC_CONTENTSTACK_PREVIEW=true npm run build && next start: pages returnCache-Control: private, no-store.