From d888e226500f1b6a65f5705979083298aacf461f Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Fri, 3 Feb 2023 01:40:07 +0800 Subject: [PATCH 1/5] add trailing slash redirect middleware --- packages/adapter-node/ambient.d.ts | 2 ++ packages/adapter-node/index.js | 7 +++--- packages/adapter-node/src/handler.js | 36 ++++++++++++++++++++++++---- 3 files changed, 38 insertions(+), 7 deletions(-) diff --git a/packages/adapter-node/ambient.d.ts b/packages/adapter-node/ambient.d.ts index c424cbb1c9f4..7d45ea6dc64a 100644 --- a/packages/adapter-node/ambient.d.ts +++ b/packages/adapter-node/ambient.d.ts @@ -8,7 +8,9 @@ declare module 'HANDLER' { declare module 'MANIFEST' { import { SSRManifest } from '@sveltejs/kit'; + export const manifest: SSRManifest; + export const prerendered: Set; } declare module 'SERVER' { diff --git a/packages/adapter-node/index.js b/packages/adapter-node/index.js index 7dc13e34d550..ac16008bc176 100644 --- a/packages/adapter-node/index.js +++ b/packages/adapter-node/index.js @@ -1,5 +1,5 @@ -import { readFileSync, writeFileSync } from 'fs'; -import { fileURLToPath } from 'url'; +import { readFileSync, writeFileSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; import { rollup } from 'rollup'; import { nodeResolve } from '@rollup/plugin-node-resolve'; import commonjs from '@rollup/plugin-commonjs'; @@ -39,7 +39,8 @@ export default function (opts = {}) { writeFileSync( `${tmp}/manifest.js`, - `export const manifest = ${builder.generateManifest({ relativePath: './' })};` + `export const manifest = ${builder.generateManifest({ relativePath: './' })};\n\n` + + `export const prerendered = new Set(${JSON.stringify(builder.prerendered.paths)});\n` ); const pkg = JSON.parse(readFileSync('package.json', 'utf8')); diff --git a/packages/adapter-node/src/handler.js b/packages/adapter-node/src/handler.js index 8166e654f328..0f127967522a 100644 --- a/packages/adapter-node/src/handler.js +++ b/packages/adapter-node/src/handler.js @@ -1,11 +1,11 @@ import './shims'; -import fs from 'fs'; -import path from 'path'; +import fs from 'node:fs'; +import path from 'node:path'; import sirv from 'sirv'; -import { fileURLToPath } from 'url'; +import { fileURLToPath } from 'node:url'; import { getRequest, setResponse } from '@sveltejs/kit/node'; import { Server } from 'SERVER'; -import { manifest } from 'MANIFEST'; +import { manifest, prerendered } from 'MANIFEST'; import { env } from 'ENV'; /* global ENV_PREFIX */ @@ -44,8 +44,35 @@ function serve(path, client = false) { ); } +// required because the static file server ignores trailing slashes +/** @type {import('polka').Middleware} */ +async function trailing_slash_redirect(req, res, next) { + let pathname = req.path; + + try { + pathname = decodeURIComponent(pathname); + } catch { + // ignore invalid URI + } + + if (pathname === '/' || prerendered.has(pathname)) { + return next(); + } + + // redirect to counterpart + const counterpart_route = pathname.at(-1) === '/' ? pathname.slice(0, -1) : pathname + '/'; + if (counterpart_route && prerendered.has(counterpart_route)) { + const location = `${counterpart_route}?${new URLSearchParams(req.query)}`; + res.writeHead(308, { location }).end(); + } else { + // continue to next middleware ... or skip to ssr? + next(); + } +} + /** @type {import('polka').Middleware} */ const ssr = async (req, res) => { + /** @type {Request | undefined} */ let request; try { @@ -139,6 +166,7 @@ export const handler = sequence( [ serve(path.join(dir, 'client'), true), serve(path.join(dir, 'static')), + trailing_slash_redirect, serve(path.join(dir, 'prerendered')), ssr ].filter(Boolean) From 017508b1f98b7517d1e69a554207735d1f65ec00 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Fri, 3 Feb 2023 01:43:47 +0800 Subject: [PATCH 2/5] add changeset --- .changeset/warm-bananas-sneeze.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/warm-bananas-sneeze.md diff --git a/.changeset/warm-bananas-sneeze.md b/.changeset/warm-bananas-sneeze.md new file mode 100644 index 000000000000..11b8fd7b937e --- /dev/null +++ b/.changeset/warm-bananas-sneeze.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/adapter-node': patch +--- + +fix: correctly redirect trailing slashes for `adapter-node` From e1a78702e948c9e8819fdf3af1669e4c832b56a2 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 2 Feb 2023 13:21:03 -0500 Subject: [PATCH 3/5] skip middleware where possible --- packages/adapter-node/src/handler.js | 46 +++++++++++++++------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/packages/adapter-node/src/handler.js b/packages/adapter-node/src/handler.js index 0f127967522a..aa759144e9f9 100644 --- a/packages/adapter-node/src/handler.js +++ b/packages/adapter-node/src/handler.js @@ -45,29 +45,32 @@ function serve(path, client = false) { } // required because the static file server ignores trailing slashes -/** @type {import('polka').Middleware} */ -async function trailing_slash_redirect(req, res, next) { - let pathname = req.path; +/** @returns {import('polka').Middleware} */ +function serve_prerendered() { + const handler = serve(path.join(dir, 'prerendered')); - try { - pathname = decodeURIComponent(pathname); - } catch { - // ignore invalid URI - } + return (req, res, next) => { + let pathname = req.path; - if (pathname === '/' || prerendered.has(pathname)) { - return next(); - } + try { + pathname = decodeURIComponent(pathname); + } catch { + // ignore invalid URI + } - // redirect to counterpart - const counterpart_route = pathname.at(-1) === '/' ? pathname.slice(0, -1) : pathname + '/'; - if (counterpart_route && prerendered.has(counterpart_route)) { - const location = `${counterpart_route}?${new URLSearchParams(req.query)}`; - res.writeHead(308, { location }).end(); - } else { - // continue to next middleware ... or skip to ssr? - next(); - } + if (prerendered.has(pathname)) { + return handler(req, res, next); + } + + // redirect to counterpart + const counterpart_route = pathname.at(-1) === '/' ? pathname.slice(0, -1) : pathname + '/'; + if (counterpart_route && prerendered.has(counterpart_route)) { + const location = `${counterpart_route}?${new URLSearchParams(req.query)}`; + res.writeHead(308, { location }).end(); + } else { + next(); + } + }; } /** @type {import('polka').Middleware} */ @@ -166,8 +169,7 @@ export const handler = sequence( [ serve(path.join(dir, 'client'), true), serve(path.join(dir, 'static')), - trailing_slash_redirect, - serve(path.join(dir, 'prerendered')), + serve_prerendered(), ssr ].filter(Boolean) ); From bbdd04c8c211247d2afc7681348317d2e08e2b15 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 2 Feb 2023 13:29:07 -0500 Subject: [PATCH 4/5] dont add ? to URLs --- packages/adapter-node/src/handler.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/adapter-node/src/handler.js b/packages/adapter-node/src/handler.js index aa759144e9f9..cd68657c1b52 100644 --- a/packages/adapter-node/src/handler.js +++ b/packages/adapter-node/src/handler.js @@ -65,7 +65,8 @@ function serve_prerendered() { // redirect to counterpart const counterpart_route = pathname.at(-1) === '/' ? pathname.slice(0, -1) : pathname + '/'; if (counterpart_route && prerendered.has(counterpart_route)) { - const location = `${counterpart_route}?${new URLSearchParams(req.query)}`; + const search = req.url.split('?')[1]; + const location = `${counterpart_route}${search ? `?${search}` : ''}`; res.writeHead(308, { location }).end(); } else { next(); From 4bbaf732b4550d0475a62117631bb8b26d118516 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 2 Feb 2023 14:03:46 -0500 Subject: [PATCH 5/5] minor tweak --- packages/adapter-node/src/handler.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/adapter-node/src/handler.js b/packages/adapter-node/src/handler.js index cd68657c1b52..650ab3ce6018 100644 --- a/packages/adapter-node/src/handler.js +++ b/packages/adapter-node/src/handler.js @@ -62,11 +62,11 @@ function serve_prerendered() { return handler(req, res, next); } - // redirect to counterpart - const counterpart_route = pathname.at(-1) === '/' ? pathname.slice(0, -1) : pathname + '/'; - if (counterpart_route && prerendered.has(counterpart_route)) { + // remove or add trailing slash as appropriate + let location = pathname.at(-1) === '/' ? pathname.slice(0, -1) : pathname + '/'; + if (prerendered.has(location)) { const search = req.url.split('?')[1]; - const location = `${counterpart_route}${search ? `?${search}` : ''}`; + if (search) location += `?${search}`; res.writeHead(308, { location }).end(); } else { next();