Next.js Rendering and Deployment Features for a Headless CMS Site
How Next.js handles ISR, middleware, and image and font loading for a CMS-backed site. Covers on-demand updates, draft previews, and fast page loads.

Next.js ships three features that matter most for a headless CMS site: on-demand revalidation, middleware, and the built-in next/image and next/font optimizers. Together they decide how fast your pages load. They also decide how fast an edit in the CMS shows up live. Static site generation (SSG) is the baseline: Next.js renders your pages to HTML at build time, then serves them from a CDN with no server work per request.
That speed comes at a cost. A pure static build only knows what existed at the last build. A CMS-backed site needs a way to catch up between builds. It also has to gate unpublished drafts and still ship fast images and fonts. This guide covers all three, and how they fit together on a real content site.
What Is SSG, and Why Does a CMS Site Still Need More?
Static site generation renders every page to HTML once, during the build. Visitors get a file from a CDN edge node, not a live server render. That's the fastest response Next.js can serve.
The catch: SSG alone means a rebuild for every content change. Publish a typo fix in your CMS, and the live site still shows the old text until the next deploy. For a five-post blog, that's a bearable wait. For hundreds of entries and multiple editors, it's a real bottleneck. Next.js closes that gap with incremental static regeneration, or ISR. ISR updates single pages without a full rebuild.
SSG vs ISR vs SSR
Three rendering modes cover most CMS-driven pages:
- SSG — built once, served static, fastest, worst freshness.
- ISR — built once, revalidated on a timer or an event, fast with acceptable freshness.
- SSR — rendered per request, always fresh, slowest under load.
Most content sites default to ISR for everything. The exception is an admin dashboard or a per-user page, where SSR earns its cost.
How Does On-Demand Revalidation Work in Next.js?
On-demand revalidation clears a cached page the moment content changes, instead of waiting for a timer to run out. Next.js gives you two functions for this: revalidatePath() for one route, and revalidateTag() for every fetch that shares a tag string.
Tag a fetch at the point you request it:
const res = await fetch('https://api.draftbase.co/delivery/entries?templateId=blogPost', {
headers: { Authorization: `Bearer ${process.env.DRAFTBASE_KEY}` },
next: { tags: ['blogPost'] },
});
Then call revalidateTag('blogPost') from a Route Handler once your CMS fires a webhook on publish. The next request to any page that used that tag gets a fresh render. Every other page stays cached.
Time-based revalidation still has its place
A plain revalidate: 3600 on a route is enough for pages that change on their own schedule, like a pricing page nobody edits daily. On-demand revalidation is for pages an editor expects to update the moment they hit publish. Running both isn't a conflict. Time-based revalidation is just the backup if a webhook ever gets dropped.
One gotcha worth naming up front: revalidateTag() only invalidates the cache on the instance that receives the call. We've covered why that matters, and the fix, in our guide to running a headless CMS without deployments — read that before wiring webhooks into a multi-instance deployment.
Using Middleware to Gate Draft Mode
Next.js Middleware runs before a request reaches a route. That makes it the natural place to check whether a visitor should see draft content at all.
Draft Mode itself is a built-in Next.js feature, not a CMS integration. Your CMS opens a URL like /api/draft?secret=XXX&slug=/blog/my-post when an editor clicks preview. A Route Handler checks the secret, calls draftMode().enable(), and redirects to the slug. From there, every fetch in that request can ask for the unpublished draft instead of the last live copy.
Middleware earns its place on top of that flow for two jobs:
- Blocking draft routes from search engines. Check
draftMode().isEnabledand inject anoindexheader. An accidentally-shared preview link should never get crawled. - Locking preview access. Middleware can check a cookie or an allowed IP before Draft Mode even runs. That way, a preview link can't become a backdoor into unpublished content.
export function middleware(request: NextRequest) {
if (request.nextUrl.pathname.startsWith('/preview') && !request.cookies.has('preview-token')) {
return NextResponse.redirect(new URL('/', request.url));
}
}
Middleware runs on the Edge Runtime, so keep the check cheap. A cookie or header check is fine. A database call on every request is not.
Draft content and Draftbase
Delivery API responses only ever return published and updated content. An entry edited after it first went live still returns its last published revision, not the pending edit. Draft preview needs a separate management API call instead, using a management-scoped key. That's the exact case Middleware plus Draft Mode is built to gate.
Optimizing Images from a Headless CMS with next/image
A CMS entry usually stores an image as a URL, not a file in your repo. next/image handles a remote URL the same way it handles a local import. You just have to allow the domain first.
// next.config.ts
export default {
images: {
remotePatterns: [{ protocol: 'https', hostname: 'assets.draftbase.co' }],
},
};
From there, next/image lazy-loads offscreen images and serves modern formats like AVIF and WebP on its own. It also stops layout shift by saving space for the image before it loads. A media field on a CMS entry usually already knows its width and height. Pass those through instead of guessing:
<Image src={entry.coverImage.url} width={entry.coverImage.width} height={entry.coverImage.height} alt={entry.coverImageAlt} />
Two settings matter more on a content site than a typical app. The sizes prop tells the browser how much of the viewport the image actually fills. Skip it, and the browser might download a 1536px hero image for a 400px thumbnail slot. priority should go on just one image above the fold, usually the hero. Mark every image a priority and you lose the whole point of lazy loading the rest.
Optimizing Fonts with next/font
next/font downloads font files at build time and hosts them itself. There's no runtime request to Google Fonts, and no outside network call at all. That removes a common cause of layout shift. The browser already knows the font's shape before the page paints, instead of swapping a fallback font for the real one mid-render.
import { Inter } from 'next/font/google';
const inter = Inter({ subsets: ['latin'], display: 'swap' });
For a CMS-backed site, this matters more than it sounds. Content pages are usually the highest-traffic, highest-bounce-risk pages on the whole site. A blog post loaded from a search result gets one shot at a good first paint. A self-hosted font with no outside request is a small, permanent speed win on every one of those page loads.
Authentication for CMS-Backed Next.js Sites
Most content sites only need auth for one real job: gating who can see draft or scheduled content before it publishes, and who can call the webhook that triggers a refresh.
That's a narrower job than a full user-login system. A shared secret checked in a Route Handler covers the webhook case. A library like NextAuth.js only earns its extra weight when the site itself has logged-in readers, say a member area or gated content, not just an editorial team publishing through the CMS.
Themes and Templates on a Content-Driven Next.js Site
A "Next.js template" search mostly turns up starter repos: a layout, a set of components, and a data-fetching pattern already wired up. For a CMS-backed build, keep the fetch layer generic. Lock a template to one CMS's response shape, and swapping vendors later means rewriting every page, not just the fetch calls.
A thin adapter layer maps your CMS's entry shape to the props your components expect. Write that once, and the swap stays cheap. It's a few dozen lines. That's the difference between changing one file and rewriting a whole template.
What Breaks in Production on a CMS-Backed Next.js Site?
A few failure modes show up more on content sites than a typical app build:
- A webhook fires before the CMS finishes its write. Run revalidation too early, and the freshly revalidated page can serve the old content once more. It then looks stale until the next edit. Add a short delay or a retry. Don't assume the webhook always wins the race.
- A stale build cache after a schema change. Adding a field to a CMS template doesn't clear any page cache in Next.js, since the data on existing entries didn't change. Widen the type. Just don't expect old cached pages to show the new field until they revalidate on their own.
- Fetch failures during ISR quietly serve the last good cache. That's usually the right default. But it also means a broken CMS API key can go unnoticed for days, since visitors never see an error, just old content.
Conclusion
Next.js gives a CMS-backed site three tools that matter more than any starter template. revalidateTag() handles fast, targeted updates. Middleware plus Draft Mode handle safe previews. next/image and next/font handle a fast first paint on every content page. None of them force you to pick Next.js over another framework. But they're a big part of why so many headless CMS setups default to it.
If you're building that stack now, Draftbase fires a webhook on every entry event. Wiring revalidateTag() to a publish is one Route Handler, not a cron job. Start free on Hobby, no card required.
Ship content that's built to be found
Draftbase generates schema, structured data, and a fast MDX editor for every post.
Frequently asked questions
Does ISR work with a headless CMS automatically?
No. ISR needs a revalidate interval on the route. Or it needs a webhook that calls revalidateTag or revalidatePath. Neither shows up without that wiring.
Do I need Middleware to use Next.js Draft Mode?
No, Draft Mode works without it. Middleware adds an extra access check before Draft Mode runs. It can also add a noindex header so preview URLs don't get crawled.
Does next/image work with images hosted outside my Next.js project?
Yes, once the image's domain is listed under remotePatterns in next.config.ts. Without that entry, next/image refuses to optimize the remote URL.
Is SSG or ISR better for a content site?
ISR wins for almost any page a non-developer can edit. Pure SSG only fits pages that never change between deploys, like a static terms page.
Does next/font require a Google Fonts account?
No. next/font/google downloads the font files at build time and hosts them itself. There's no runtime call to Google's servers, and no account needed.