[]previous and next page

CDN, caching and what the platform owns

CDN, caching and what the platform owns in the Deploy guide.

For agentsUpdated

Built assets on a CDN. dusk({ assetOrigin: "https://cdn.example" }) makes the built bundles, the CSS and the files of the assets folder refer to <origin><base><file> (for base: "/", https://cdn.example/assets/app-….js). The CDN must serve those files at that path: upload the assets/ folder of dist/client to it. Routes, public/ files and the server stay on the app's own origin, and base still sets the route mount prefix. Without the option, nothing changes. The option is validated: an origin only, with no path, query or credentials.

Immutable files. Every adapter sends Cache-Control: public, max-age=31536000, immutable for the build's assets folder and for fonts/google/ (the Google fonts Dusk vendors, named by their content hash): the _headers files of Netlify and Cloudflare, the routes of Vercel's config.json, and the Node static server's default. The Vite manifest (.vite/manifest.json) is read by the build and then removed, so no platform output carries it.

Pages on the CDN. An ISR page (revalidate) sends Cache-Control: public, max-age=0, s-maxage=<lifetime>, stale-while-revalidate=<stale window> and an Age (seconds since its render began). A shared cache subtracts Age from s-maxage, so it keeps the page for what is left of its fresh window and expires it when the server does; a CDN that ignores Age keeps it up to one lifetime longer than the server. A page with revalidate: false sends s-maxage=31536000 instead of must-revalidate alone, so a CDN keeps it until a purge or a new build. A dynamic page (no revalidate) is not cached by Dusk: the handler answers private, no-store. A route rule can ask for the CDN's own copy of the same policy: routeRules: { "/news": { revalidate: 60, cache: { cdn: true } } } sends CDN-Cache-Control with the same directives, and cache: { cdn: { surrogate: true } } also sends Surrogate-Control. A page keyed on cookies is private and sends neither.

Vary. A page that varies on a request header (a locale negotiated from Accept-Language) is cached once per value of that header: the Vary names are stored with the page, and each value gets its own entry, so two languages on one path never share a body. A page that varies on * is answered but not stored. A request with a cookie the page does not declare bypasses the cache. The i18n middleware's Vary: Accept-Language is read the same way. Every compressible answer (HTML, JSON, text, SVG) carries Vary: Accept-Encoding, whether or not Dusk compresses it.

Purge is in-process. revalidatePath and revalidateTag remove entries from the page cache of the process that calls them, or from the shared store when cache.kv or cache.store (for example redisStore) is set. They do not send a purge to a CDN. After a deploy, purge the CDN with its own API, or let the CDN's short max-age=0 revalidation catch up.

Compression on serverless. Dusk compresses on Node (buffered and streamed text). Bun and Deno compress buffered text with a known length only. On Vercel, Netlify and Cloudflare the platform compresses: Dusk does not, and the adapter test suite does not measure it.

Early Hints. The Node server sends 103 Early Hints for page requests (see Early Hints). On the other platforms the Link preload headers come with the HTML a handler renders; a platform that supports Early Hints can turn them into a 103 in front.

Service workers. Dusk does not set Service-Worker-Allowed. A service worker's scope is the path of its file (/sw.js controls /). A wider or narrower scope is a header the app sets on that file, through the route rules' headers.

Image optimizer and DNS. /_dusk/image refuses, even when the origin is allowed, a literal address in a loopback, private, link-local, CGNAT or unique-local range, and localhost, *.localhost, *.local and *.internal names. It resolves a remote name and refuses it when any answer is such an address, at the connect step: on Node that is safeImageFetch, the default there (see the security guide). On Bun, Deno and Workers the platform's fetch is used, and the framework does not check the answers of a name: refuse private answers at the platform or the egress firewall. Its default qualities is [75]; list the others in images.qualities to serve them.