diff --git a/.changeset/adapter-node-manifest-mime-types.md b/.changeset/adapter-node-manifest-mime-types.md new file mode 100644 index 000000000000..1d59ca72c56b --- /dev/null +++ b/.changeset/adapter-node-manifest-mime-types.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/adapter-node': patch +--- + +fix: serve static files with the Content-Type recorded in the manifest diff --git a/.changeset/kit-prerendered-mime-types.md b/.changeset/kit-prerendered-mime-types.md new file mode 100644 index 000000000000..c368d781dcb8 --- /dev/null +++ b/.changeset/kit-prerendered-mime-types.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': patch +--- + +fix: record the mime types of prerendered paths in the server manifest diff --git a/packages/adapter-node/src/handler.js b/packages/adapter-node/src/handler.js index 5c6739634263..a8ac21dd05d5 100644 --- a/packages/adapter-node/src/handler.js +++ b/packages/adapter-node/src/handler.js @@ -45,17 +45,21 @@ function serve(path, client = false) { etag: true, gzip: PRECOMPRESS, brotli: PRECOMPRESS, - setHeaders: client - ? (res, pathname) => { - // only apply to build directory, not e.g. version.json - if ( - pathname.startsWith(`/${manifest.appPath}/immutable/`) && - res.statusCode === 200 - ) { - res.setHeader('cache-control', 'public,max-age=31536000,immutable'); - } - } - : undefined + setHeaders: (res, pathname) => { + // `sirv` uses its own bundled `mrmime`, which the manifest's added types never reach + let type = manifest.mimeTypes[pathname.slice(pathname.lastIndexOf('.'))]; + if (type === 'text/html') type += ';charset=utf-8'; + if (type) res.setHeader('content-type', type); + + // only apply to build directory, not e.g. version.json + if ( + client && + pathname.startsWith(`/${manifest.appPath}/immutable/`) && + res.statusCode === 200 + ) { + res.setHeader('cache-control', 'public,max-age=31536000,immutable'); + } + } }) : undefined; } diff --git a/packages/adapter-node/test/apps/basic/src/routes/prerendered.ico/+server.js b/packages/adapter-node/test/apps/basic/src/routes/prerendered.ico/+server.js new file mode 100644 index 000000000000..ea5bb293b791 --- /dev/null +++ b/packages/adapter-node/test/apps/basic/src/routes/prerendered.ico/+server.js @@ -0,0 +1,7 @@ +export const prerender = true; + +export function GET() { + return new Response(new Uint8Array([0, 0, 1, 0]), { + headers: { 'content-type': 'image/x-icon' } + }); +} diff --git a/packages/adapter-node/test/apps/basic/static/page.html b/packages/adapter-node/test/apps/basic/static/page.html new file mode 100644 index 000000000000..3ec9ee860d0f --- /dev/null +++ b/packages/adapter-node/test/apps/basic/static/page.html @@ -0,0 +1,2 @@ + +
hi
diff --git a/packages/adapter-node/test/apps/basic/static/test.ico b/packages/adapter-node/test/apps/basic/static/test.ico new file mode 100644 index 000000000000..5b94bb90acc5 Binary files /dev/null and b/packages/adapter-node/test/apps/basic/static/test.ico differ diff --git a/packages/adapter-node/test/apps/basic/test/test.js b/packages/adapter-node/test/apps/basic/test/test.js index a3bae54d9c1f..5f09a35b8f9d 100644 --- a/packages/adapter-node/test/apps/basic/test/test.js +++ b/packages/adapter-node/test/apps/basic/test/test.js @@ -22,3 +22,24 @@ test('does not set X-Accel-Buffering header on other responses', async ({ reques const response = await request.get('/'); expect(response.headers()['x-accel-buffering']).toBeUndefined(); }); + +test('serves static files with the Content-Type from the manifest', async ({ request }) => { + // https://github.com/sveltejs/kit/issues/13753 + const response = await request.get('/test.ico'); + expect(response.status()).toBe(200); + expect(response.headers()['content-type']).toBe('image/x-icon'); +}); + +test('serves prerendered endpoints with the Content-Type from the manifest', async ({ + request +}) => { + const response = await request.get('/prerendered.ico'); + expect(response.status()).toBe(200); + expect(response.headers()['content-type']).toBe('image/x-icon'); +}); + +test('serves static HTML with a charset', async ({ request }) => { + const response = await request.get('/page.html'); + expect(response.status()).toBe(200); + expect(response.headers()['content-type']).toBe('text/html;charset=utf-8'); +}); diff --git a/packages/kit/src/core/generate_manifest/index.js b/packages/kit/src/core/generate_manifest/index.js index b83aa8fef7e4..f524e1da70d2 100644 --- a/packages/kit/src/core/generate_manifest/index.js +++ b/packages/kit/src/core/generate_manifest/index.js @@ -94,6 +94,12 @@ export function generate_manifest({ mime_types[ext] ??= mime_lookup(ext) || ''; } + // record extensions that only exist in prerendered output, e.g. a prerendered favicon.ico + for (const pathname of prerendered) { + const ext = path.extname(pathname); + if (ext) mime_types[ext] ??= mime_lookup(ext) || ''; + } + // prettier-ignore // String representation of /** @template {import('@sveltejs/kit').SSRManifest} T */