Rendering and page caching
Visitors get cached pages, and editors see their changes without waiting for a timeout.
Problem
Server-rendered pages that read the database on every visit cost a render and a query per request. Caching them for a fixed time means an editor publishes and then waits, or asks a developer to clear a cache. Rebuilding the whole site on each change is slow, and ties editing to your build.
Approach
Krantho knows two things that make caching precise instead of timed. Every page read records cache tags for what it read: a document, a collection list, an SEO configuration, the site. Every committed write announces what it changed, and invalidationsFor() in @krantho/core maps that change to the tags it invalidates. A cached page is kept until something it shows changes, and then only the pages that show it are refreshed.
One configuration leaf, cache.pages, picks the strategy:
nonerenders every request. This is the default.next-isrkeeps each Next.js page, rendered on its first request, in Next's cache until a change invalidates its tags. Behind CloudFront or Cloud CDN, the API also purges the CDN after a change.cdnlets the CDN in front of a SvelteKit or Express site keep each page. The page carries its tags in a header, and a change purges those tags at Vercel, CloudFront or Google Cloud CDN.
Several Next.js instances without a cache the platform shares can keep Next's cache in the site's own database, SQLite or Postgres, with cache.shared: 'database'. A page one instance rendered is served by the others, and an invalidation on one reaches all of them.
A save changes nothing that visitors see, so it refreshes nothing.
Proof
The build never reads the database. Content routes are dynamic segments that render on their first request:
export const revalidate = false
export function generateStaticParams(): { slug: string[] }[] {
return []
}This site runs this way: a Next.js app on Vercel with cache.pages: 'next-isr'. Each page is rendered once, served from the cache, and refreshed when its content changes in the admin.
import { defineKranthoConfig } from '@krantho/config'
export default defineKranthoConfig({
site: {
domain: 'krantho.com',
name: 'Krantho',
language: 'en',
baseUrl: 'https://krantho.com',
},
cache: {
// Each page renders on its first request and stays cached until a change
// in /admin invalidates it. Content routes need the ISR shape:
// generateStaticParams returns [] and revalidate is false.
pages: 'next-isr',
// Several `next start` instances: keep Next's cache in the site's database,
// with the cache handler from @krantho/server-next set in next.config.ts
// shared: 'database',
},
})import type { Handle } from '@sveltejs/kit'
import { createPageCacheHandle } from '@krantho/server-sveltekit'
import config from '../krantho.config'
// Pages carry CDN cache headers and cache tags; a change purges them.
// Keep this the outermost handle, first in sequence().
export const handle: Handle = createPageCacheHandle({ config })
// The CDN is chosen by the environment, with no code change:
// KRANTHO_CACHE_PAGES=cdn
// KRANTHO_CACHE_CDN_PROVIDER=cloudfront # or gcp, or vercel
// KRANTHO_CACHE_CDN_CLOUDFRONT_DISTRIBUTION_ID=E2EXAMPLE// server.ts, continued: `app` and `config` come from the API wiring
import { renderCachedPage } from '@krantho/server-node'
// Every page goes through renderCachedPage. render() is your SSR entry,
// returning { status, html, headers? }; the helper records the cache tags of
// the reads inside it and sets the CDN headers. A change purges those tags.
app.use(async (req, res) => {
const request = { url: req.originalUrl, method: req.method }
const page = await renderCachedPage(request, config, () => render(req.originalUrl))
for (const [name, value] of Object.entries(page.headers)) res.setHeader(name, value)
res.status(page.status).type('html').send(page.html)
})
// The CDN is chosen by the environment, with no code change:
// KRANTHO_CACHE_PAGES=cdn
// KRANTHO_CACHE_CDN_PROVIDER=gcp # or cloudfront, or vercel
// KRANTHO_CACHE_CDN_GCP_PROJECT=my-project
// KRANTHO_CACHE_CDN_GCP_URL_MAP=my-url-mapLimits
next-isris for Next.js;cdnis for SvelteKit and Express.- The CDN strategies need the provider's setup on the host side, such as a cache policy that honors the origin's headers. The page caching guide lists what each provider needs.
- A failed CDN purge never fails the write. It is logged, and the page expires after its CDN lifetime, one day by default.
- Content files that a deploy ships are not reliably purged at a CDN. Purge the CDN as a deploy step when content files change.
- A page carries at most 50 cache tags. Beyond that, document tags give way to their collection's tag, so a page may refresh more often than it needs to.