[]previous and next page

Static files

Static files in the Deploy guide.

For agentsUpdated

static takes a StaticOptions. Paths are decoded once, checked for dot segments, NUL bytes and backslashes, and resolved with realpath below the real directory, so neither ..%2f nor a symlink leads out. Only GET and HEAD are answered; a path that is not a file falls through to the handler. When security.allowedHosts is set, the Host is checked before any file is read: a refused Host gets the handler's 400 for every path (see Platform host lists).

StaticOptions Default Meaning
dir required directory to serve
prefix "/" URL prefix it is mounted at (/app for a build with base: "/app/")
immutable a file name with a hash segment of 8 or more characters (and a digit, unless it sits under assetsDir), or a font under fonts/google/ (named by its content hash) Cache-Control: public, max-age=31536000, immutable
assetsDir "assets" the build's assets folder (Vite's build.assetsDir); the default immutable rule counts a hashed file under it
maxAgeSeconds 0 max-age of every other file, which always revalidates (must-revalidate) with its ETag
pages none the prerendered pages and how each is answered: static (the file, with the app's headers) or handler (rendered per request, the file is never read). The build fills it; see the Vercel section
index none file served for a directory ("index.html" for a static site)
extensions none extensions tried for a path without one ([".html"] serves /about from about.html)
dotfiles "deny" "allow" serves paths with a segment that starts with a dot
wellKnown security.txt, assetlinks.json, apple-app-site-association, change-password, openid-configuration names served from public/.well-known/<name>; no subpaths. Other dotfiles, .env* included, stay denied
precompressed true use file.br, file.zst or file.gz next to the original when the client accepts it

Every response has a weak ETag, Last-Modified and X-Content-Type-Options: nosniff; If-None-Match and If-Modified-Since give 304; a single Range gives 206 (not on compressed variants); Vary: Accept-Encoding is set on compressible types, whether or not the server compresses.

Precedence. public/ is served before the handler, so public/robots.txt answers /robots.txt and a route at the same path is never reached. When a route and a public file must answer different things, give them different paths. To run the global middleware for these files (an auth gate on public/ or on the build's assets), set runMiddlewareForStaticFiles: true; see Files and the global middleware.

Dot paths. Any path with a dot segment is refused, except /.well-known/<name> for a name in wellKnown, which is served as a plain file (apple-app-site-association and openid-configuration as JSON). A traversal spelled with encoded dots or slashes (%2e%2e, ..%2f, %252e%252e) is refused, or left to the handler; it never reads a file outside the folder.

Prerendered pages. The generated Node server passes index: "index.html" and extensions: [".html"]: /about answers about/index.html (or about.html) and / answers index.html, with the static cache headers above. The generated Bun and Deno servers answer the same files. Their static handling is narrower than Node's: no Range, no If-Modified-Since, no precompressed siblings (deferred, see Other runtimes).