Describe the bug
With @sveltejs/adapter-node 6, prerendered pages are served without a Content-Type header. Static files and client assets still get theirs.
builder.mimeTypes (packages/kit/src/core/adapt/builder.js) collects the extensions it needs from prerendered.paths and the client output:
for (const file of [...prerendered.paths, ...walk(path.join(build_data.out_dir, 'client'))]) {
prerendered.paths are URL paths (/, /about), which have no extension, and the pages' .html files are in the prerendered output, not the client output. So .html only reaches the table when some file in static/ happens to end in .html. Otherwise the static server added in #16908 sends the pages without a Content-Type.
On plain node build this goes unnoticed, because browsers sniff the HTML. With X-Content-Type-Options: nosniff, which is common via Helmet, a proxy or a custom server wrapping handler, Chromium renders every prerendered page as plain text.
The adapter-node test app doesn't catch it: its static/page.html puts .html into the table, which is why the serves prerendered pages and redirects to their canonical path test passes.
Reproduction
From a fresh project:
npx sv create repro --template minimal --types ts --no-add-ons --install npm
cd repro
npx sv add sveltekit-adapter=adapter:node --install npm
echo "export const prerender = true;" > src/routes/+layout.ts
npm run build && PORT=3000 node build
curl -sI http://localhost:3000/ | grep -i content-type # nothing
curl -sI http://localhost:3000/robots.txt | grep -i content-type # content-type: text/plain
grep -oE 'mime_types = \{[^}]*\}' build/adapter-node.js
# mime_types = {".txt":"text/plain",".json":"application/json",".js":"text/javascript"}
To see the effect in a browser, serve the same build with nosniff:
// nosniff.js
import { createServer } from 'node:http';
import { handler } from './build/handler.js';
createServer((req, res) => {
res.setHeader('X-Content-Type-Options', 'nosniff');
handler(req, res);
}).listen(3001);
node nosniff.js, then open http://localhost:3001/: Chromium shows the page source as text, and document.contentType is text/plain.
Logs
No errors. The pages are a 200 without a content-type header.
System Info
System:
OS: macOS 27.0.1
Binaries:
Node: 24.19.0
npm: 11.17.0
npmPackages:
@sveltejs/adapter-node: ^6.0.0 => 6.0.0
@sveltejs/kit: ^3.0.0 => 3.0.1
@sveltejs/vite-plugin-svelte: ^7.2.0 => 7.3.1
svelte: ^5.57.1 => 5.57.2
vite: ^8.3.0 => 8.3.3
Severity
serious, but I can work around it
Additional Information
Also taking the extensions from the pages' files fixes it. Patched into the repro above, the table gains ".html":"text/html" and the pages are served as text/html;charset=utf-8:
for (const file of [
...prerendered.paths,
...Array.from(prerendered.pages.values(), (page) => page.file),
...walk(path.join(build_data.out_dir, 'client'))
]) {
A test app without an .html file in static/ would cover it.
Describe the bug
With
@sveltejs/adapter-node6, prerendered pages are served without aContent-Typeheader. Static files and client assets still get theirs.builder.mimeTypes(packages/kit/src/core/adapt/builder.js) collects the extensions it needs fromprerendered.pathsand the client output:prerendered.pathsare URL paths (/,/about), which have no extension, and the pages'.htmlfiles are in the prerendered output, not the client output. So.htmlonly reaches the table when some file instatic/happens to end in.html. Otherwise the static server added in #16908 sends the pages without aContent-Type.On plain
node buildthis goes unnoticed, because browsers sniff the HTML. WithX-Content-Type-Options: nosniff, which is common via Helmet, a proxy or a custom server wrappinghandler, Chromium renders every prerendered page as plain text.The adapter-node test app doesn't catch it: its
static/page.htmlputs.htmlinto the table, which is why theserves prerendered pages and redirects to their canonical pathtest passes.Reproduction
From a fresh project:
To see the effect in a browser, serve the same build with
nosniff:node nosniff.js, then open http://localhost:3001/: Chromium shows the page source as text, anddocument.contentTypeistext/plain.Logs
System Info
Severity
serious, but I can work around it
Additional Information
Also taking the extensions from the pages' files fixes it. Patched into the repro above, the table gains
".html":"text/html"and the pages are served astext/html;charset=utf-8:A test app without an
.htmlfile instatic/would cover it.