Caching and preview
The site template's ISR and cache layers, panel-triggered revalidation, the preview site, redirects, language prefixes, and the sitemap.
This page covers how the site caches content, how a change made in the panel reaches the site, and how the preview site differs from the live site.
Two cache layers
The site template has two separate caches. If either one isn't cleared, stale content stays visible.
| Layer | What it holds | Lifetime |
|---|---|---|
| Fetch cache | CMS responses, all written with the cms-api tag. | 300 seconds |
| ISR cache | HTML generated from pages. | 300 seconds (revalidate = 300) |
export const CMS_CACHE_TAG = "cms-api";
export const CMS_REVALIDATE_SECONDS = 300;Even if nothing triggers revalidation, a page refreshes itself within five minutes. sitemap.xml, robots.txt, and the redirect map are cached for the same duration.
Revalidation
The /api/revalidate route clears both layers together: it expires the cms-api tag, then invalidates pages. With ?path=/blog/post, only that path is invalidated. Without it, every page is.
revalidateTag(CMS_CACHE_TAG, "max");
const path = new URL(request.url).searchParams.get("path");
if (path) revalidatePath(path);
else revalidatePath("/", "layout");After saving a page, sending it for approval, approving, rejecting, deleting, or reordering, the panel calls /api/revalidate on both the preview site and the live site. This call always refreshes the whole site. The Refresh Cache button in the panel triggers the same refresh manually; after use, the button is disabled for 60 seconds. Because the panel runs on a different domain, the origins allowed to make that call are set in REVALIDATE_ALLOWED_ORIGINS.
When the site runs on several server instances, each instance keeps its ISR cache on its own disk. List the other instances in REVALIDATE_NODES, and revalidation requests are forwarded to them.
Scheduled publishing
Scheduled publishing and unpublishing run in a CMS background job that doesn't call the site. These changes appear on the site once the ISR window expires. The delivery API's GET /api/v1/page/public-revision endpoint returns a fingerprint of the published content, which a site can watch to notice changes sooner.
When the CMS is unreachable
When the CMS can't be reached, the site doesn't report "page not found". The error propagates and ISR keeps serving the last good copy. After an outage, the site briefly stops calling the CMS at all, so visitors don't wait on timeouts.
The site's own health endpoint, /api/health, always returns 200. Whether the CMS is reachable is reported in the body:
{ "status": "ok", "preview": false, "cms": { "reachable": true, "unreachableSince": null, "lastSuccessAt": "…" } }In production, the site warms its pages from sitemap.xml on startup.
Preview site and live site
The same template is deployed twice: once as the preview site and once as the live site.
| Preview site | Live site | |
|---|---|---|
PREVIEW_MODE | true | not set |
| Content | Preview copy (draft) | Published |
| Caching | Dynamic render on every request, no cache | ISR and fetch cache |
NEXT_PUBLIC_INSPECTOR_ENABLED | true | not set |
robots.txt | Blocks the whole site | Built from CMS parameters |
PREVIEW_MODE is read on the server only. With the inspector flag off, inspector code never reaches the live bundle. To let the panel open the site in an iframe, the frame-ancestors header is derived from the site URL. Panels on other domains are configured in next.config.ts.
Redirect rules apply on the preview site too. A page with a redirect doesn't open at its own address in preview either.
Redirects
Redirects happen in the proxy, before rendering. The map is built from the CMS list at GET /api/v1/redirect/all/{language}. Chains are collapsed to a single hop and loops are dropped. The proxy runs in this order:
Static files are skipped.
Malformed URL leftovers are cleaned up with a 301.
The redirect map is applied.
Uppercase paths are redirected to lowercase with a 301.
The language prefix is resolved and the request continues to the content route.
Only navigation requests are redirected, so a form POST never gets a 301 at an old address. If the redirect lookup fails, navigation is not blocked.
The content route adds a safety net for anything the proxy misses. A page with redirectTo set is never rendered with content. It redirects to the target through the same map.
Language prefixes
The unprefixed language is DEFAULT_LANGUAGE in src/lib/template-config.ts. Other languages use a URL prefix, such as /en/about. Enabled languages are listed in TEMPLATE_LANGUAGES in src/lib/middleware/languages.ts.
export const TEMPLATE_LANGUAGES: TemplateLanguage[] = [
{ code: "en", name: "English" },
{ code: "tr", name: "Turkish" },
];If DEFAULT_LANGUAGE is missing from this list, the module throws on startup. That's deliberate: a wrong language prefix would silently break redirects.
Sitemap, robots, and IndexNow
sitemap.xmlis built from the CMS page tree. Pages that are unpublished, not yet or no longer within their publish dates, markednoIndex, preview copies, or redirects are left out. Add any other paths you want to exclude toSITEMAP_EXCLUDED_PATHS.robots.txtis built from the CMS's public parameters:ROBOTS_AI_CRAWLERSsets the policy for AI crawlers, andROBOTS_EXTRA_DISALLOWadds blocked paths. Don't add apublic/robots.txtfile; it overrides the route./indexnow.txtreturns the CMSINDEXNOW_KEYparameter as plain text. When IndexNow is enabled, the CMS notifies search engines of changed URLs, and the search engine verifies the key against this file.