Skip to content

fix: generate edge middleware to run reroute #12296

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 78 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
6d536c4
wip
eltigerchino Jun 4, 2024
58db3ce
fix types
eltigerchino Jun 4, 2024
12c1dc5
use import alias instead of builder.copy replace
eltigerchino Jun 5, 2024
5682120
add netlify support
eltigerchino Jun 5, 2024
91aeaaf
oops we only need one edge function for serverless split
eltigerchino Jun 5, 2024
06337fd
readability
eltigerchino Jun 5, 2024
7eeb691
Merge branch 'main' into fix-reroute-split
eltigerchino Jun 6, 2024
25c38a0
cleanup vercel implementation
eltigerchino Jun 6, 2024
28e7f39
this can be sync
eltigerchino Jun 6, 2024
766d4f7
make temp file a variable
eltigerchino Jun 7, 2024
2f40af2
fix node types with @vercel/edge
eltigerchino Jun 8, 2024
6ccef50
add separate tsconfig for edge files
eltigerchino Jun 8, 2024
322ba6c
prettier
eltigerchino Jun 8, 2024
2306806
cleanup netlify
eltigerchino Jun 9, 2024
5cfc8eb
docs
eltigerchino Jun 16, 2024
d8c29f3
changeset
eltigerchino Jun 16, 2024
f28e444
Merge branch 'main' into fix-reroute-split
eltigerchino Jul 24, 2024
58dfb9d
Update .changeset/hot-guests-enjoy.md
eltigerchino Oct 10, 2024
2302812
Merge branch 'main' into fix-reroute-split
eltigerchino Oct 10, 2024
2cfc6c6
Merge branch 'main' into fix-reroute-split
eltigerchino Oct 29, 2024
1f9d964
fix broken lockfile
eltigerchino Oct 29, 2024
ab4705e
Merge branch 'main' into fix-reroute-split
eltigerchino Dec 2, 2024
2d82cac
check if reroute hook exists before generating reroute middleware
eltigerchino Dec 2, 2024
2125115
bump @vercel/edge to 1.1.2
eltigerchino Dec 2, 2024
59b5f4b
copy over reroute.js
eltigerchino Dec 2, 2024
dc440c1
how do I get esbuild to bundle vercel/edge?
eltigerchino Dec 2, 2024
1d1a67d
add rollup to bundle @vercel/edge
eltigerchino Dec 3, 2024
c19a4ae
format
eltigerchino Dec 3, 2024
1a88cfa
Merge branch 'main' into fix-reroute-split
eltigerchino Jan 17, 2025
e75d042
Merge branch 'main' into fix-reroute-split
eltigerchino Jan 21, 2025
aa125d5
disable duplicate import eslint rule for line
eltigerchino Jan 21, 2025
014e5dd
bump @vercel/edge
eltigerchino Jan 21, 2025
11c4a70
strip sveltekit url internals before passing url to reroute
eltigerchino Jan 21, 2025
e9a2f85
revert
eltigerchino Jan 21, 2025
a589063
Update .changeset/hot-guests-enjoy.md
eltigerchino Jan 21, 2025
46ac4df
Update documentation/docs/25-build-and-deploy/90-adapter-vercel.md
eltigerchino Jan 21, 2025
f965bc0
Merge branch 'main' into fix-reroute-split
eltigerchino Jan 21, 2025
bb7418f
restore original path and export middleware reroute function
eltigerchino Jan 23, 2025
33c763f
Merge branch 'main' into fix-reroute-split
eltigerchino Jan 23, 2025
1da5a79
remove logs
eltigerchino Jan 23, 2025
d13fb42
bump adapter kit peer version
eltigerchino Jan 23, 2025
25a88d0
format
eltigerchino Jan 23, 2025
435e12e
reword changeset
eltigerchino Jan 23, 2025
8d667e6
fix incorrect merge
eltigerchino Jan 23, 2025
95d0d42
format
eltigerchino Jan 23, 2025
4fb32a5
apparently the edge middleware preserves the original url so we don't…
eltigerchino Jan 23, 2025
9934caa
format
eltigerchino Jan 23, 2025
705f2d1
fix merge discrepencies
eltigerchino Jan 23, 2025
a352d7c
Merge branch 'main' into fix-reroute-split
eltigerchino Jan 24, 2025
e38a660
fix endless loop on Netlify
eltigerchino Jan 24, 2025
5783e9b
restore original path
eltigerchino Jan 24, 2025
09a744b
format
eltigerchino Jan 24, 2025
9fcdfc1
Apply suggestions from code review
dummdidumm Jan 31, 2025
019c9c7
Merge branch 'main' into fix-reroute-split
dummdidumm Feb 4, 2025
d500629
fix import
eltigerchino Feb 5, 2025
826bdd1
kit changeset
eltigerchino Feb 5, 2025
08000bd
Update packages/adapter-netlify/package.json
eltigerchino Feb 12, 2025
32af931
Update packages/adapter-vercel/package.json
eltigerchino Feb 12, 2025
0aa33e7
Merge branch 'main' into fix-reroute-split
eltigerchino Feb 20, 2025
a984fff
await setResponse
eltigerchino Feb 20, 2025
849c8ed
it wasn't awaited before so let's not await it
eltigerchino Feb 20, 2025
2ce404d
Merge branch 'main' into fix-reroute-split
eltigerchino Mar 6, 2025
1a29182
skip reroute based on the manifest value
eltigerchino Mar 6, 2025
d31d06f
oops forgot this
eltigerchino Mar 6, 2025
95037c3
fix
eltigerchino Mar 6, 2025
0f997ed
ensure reroute does not run twice even if resolved path is the same
eltigerchino Mar 7, 2025
e038a3b
Merge branch 'main' into fix-reroute-split
eltigerchino Apr 9, 2025
eaa8586
fix doc links
eltigerchino Apr 9, 2025
df46216
avoid rewrite if pathname is the same
eltigerchino Apr 9, 2025
40df48a
check value of reroute export
eltigerchino Apr 9, 2025
d0fd71b
generate types
eltigerchino Apr 9, 2025
b86bfad
better docs
eltigerchino Apr 10, 2025
d73f37f
fix netlify infinite loop
eltigerchino Apr 11, 2025
2ea355c
add netlify test
eltigerchino Apr 11, 2025
509a5d8
format
eltigerchino Apr 11, 2025
2536de0
Merge branch 'main' into fix-reroute-split
eltigerchino Apr 14, 2025
6c99e04
fix lint
eltigerchino Apr 14, 2025
7f226de
Merge branch 'main' into fix-reroute-split
eltigerchino Jun 4, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/hot-guests-enjoy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@sveltejs/adapter-netlify": major
"@sveltejs/adapter-vercel": major
---

fix: run `reroute` in an edge middleware if the app has been split into multiple functions
5 changes: 5 additions & 0 deletions .changeset/modern-dogs-tie.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/kit': minor
---

feat: add `applyReroute` and `builder.getReroutePath` helpers for running `reroute` in a middleware before the main handler
2 changes: 2 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
"**/.custom-out-dir/**",
"**/build/**",
"**/test-results/**",
"**/.netlify/**",
"**/dist/**",
"documentation/**/*.md",
"packages/package/test/fixtures/**/expected/**/*",
"packages/package/test/watch/expected/**/*",
Expand Down
6 changes: 6 additions & 0 deletions documentation/docs/25-build-and-deploy/80-adapter-netlify.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,12 @@ Additionally, you can add your own Netlify functions by creating a directory for
directory = "functions"
```

## Notes

### Individual functions and `reroute`

If the `split` option is set to `true` in the adapter config, the [`reroute`](hooks#universal-hooks-reroute) function will be deployed as an edge middleware that runs before any individual function.

## Troubleshooting

### Accessing the file system
Expand Down
4 changes: 4 additions & 0 deletions documentation/docs/25-build-and-deploy/90-adapter-vercel.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,10 @@ If you have Vercel functions contained in the `api` directory at the project's r

Projects created before a certain date may default to using an older Node version than what SvelteKit currently requires. You can [change the Node version in your project settings](https://vercel.com/docs/concepts/functions/serverless-functions/runtimes/node-js#node.js-version).

### Individual functions and `reroute`

If the `split` option is set to `true` for a route, or at the adapter level, the [`reroute`](hooks#universal-hooks-reroute) function will be deployed as an edge middleware that runs before any individual function.

## Troubleshooting

### Accessing the file system
Expand Down
6 changes: 6 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ export default [
'**/test-results',
'**/build',
'**/.custom-out-dir',
'**/.wrangler',
'**/.netlify',
'**/dist',
'packages/adapter-*/files'
]
},
Expand All @@ -31,9 +34,12 @@ export default [
},
ignores: [
'packages/adapter-cloudflare/test/apps/**/*',
'packages/adapter-netlify/test/apps/**/*',
'packages/adapter-netlify/rollup.config.js',
'packages/adapter-node/rollup.config.js',
'packages/adapter-node/tests/smoke.spec_disabled.js',
'packages/adapter-static/test/apps/**/*',
'packages/adapter-vercel/rollup.config.js',
'packages/create-svelte/shared/**/*',
'packages/create-svelte/templates/**/*',
'packages/kit/src/core/sync/create_manifest_data/test/samples/**/*',
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,11 @@
},
"pnpm": {
"onlyBuiltDependencies": [
"@parcel/watcher",
"esbuild",
"netlify-cli",
"sharp",
"unix-dgram",
"svelte-preprocess",
"workerd"
]
Expand Down
148 changes: 91 additions & 57 deletions packages/adapter-netlify/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,6 @@ import toml from '@iarna/toml';
* } & toml.JsonMap} NetlifyConfig
*/

/**
* TODO(serhalp) Replace this custom type with an import from `@netlify/edge-functions`,
* once that type is fixed to include `excludedPath` and `function`.
* @typedef {{
* functions: Array<
* | {
* function: string;
* path: string;
* excludedPath?: string | string[];
* }
* | {
* function: string;
* pattern: string;
* excludedPattern?: string | string[];
* }
* >;
* version: 1;
* }} HandlerManifest
*/

const name = '@sveltejs/adapter-netlify';
const files = fileURLToPath(new URL('./files', import.meta.url).href);

Expand Down Expand Up @@ -102,14 +82,24 @@ export default function ({ split = false, edge = edge_set_in_env_var } = {}) {
`\n\n/${builder.getAppPath()}/immutable/*\n cache-control: public\n cache-control: immutable\n cache-control: max-age=31536000\n`
);

let reroute_middleware = false;

if (edge) {
if (split) {
throw new Error('Cannot use `split: true` alongside `edge: true`');
}

await generate_edge_functions({ builder });
await generate_edge_functions({ builder, reroute_middleware });
} else {
generate_lambda_functions({ builder, split, publish });
/** @type {string | void} */
let reroute_path;

if (split && (reroute_path = await builder.getReroutePath?.())) {
await generate_reroute_middleware({ builder, reroute_path });
reroute_middleware = true;
}

generate_lambda_functions({ builder, split, publish, reroute_middleware });
}
},

Expand All @@ -127,18 +117,21 @@ export default function ({ split = false, edge = edge_set_in_env_var } = {}) {
}
};
}

/**
* @param { object } params
* @param {import('@sveltejs/kit').Builder} params.builder
* @param {boolean} params.reroute_middleware
*/
async function generate_edge_functions({ builder }) {
async function generate_edge_functions({ builder, reroute_middleware }) {
const tmp = builder.getBuildDirectory('netlify-tmp');
builder.rimraf(tmp);
builder.mkdirp(tmp);

builder.mkdirp('.netlify/edge-functions');

builder.log.minor('Generating Edge Function...');

const relativePath = posix.relative(tmp, builder.getServerDirectory());

builder.copy(`${files}/edge.js`, `${tmp}/entry.js`, {
Expand All @@ -148,20 +141,79 @@ async function generate_edge_functions({ builder }) {
}
});

const manifest = builder.generateManifest({
relativePath
await bundle_edge_function({ builder, name: 'render', reroute_middleware });
}

/**
* @param {object} params
* @param {import('@sveltejs/kit').Builder} params.builder
* @param {string} params.reroute_path
*/
async function generate_reroute_middleware({ builder, reroute_path }) {
builder.log.minor('Generating edge middleware to run reroute before split functions...');

const tmp = builder.getBuildDirectory('netlify-tmp');
builder.rimraf(tmp);
builder.mkdirp(tmp);

builder.mkdirp('.netlify/edge-functions');

builder.copy(`${files}/reroute.js`, `${tmp}/entry.js`, {
replace: {
__HOOKS__: reroute_path
}
});

await bundle_edge_function({ builder, name: 'reroute', reroute_middleware: false });
}

/**
*
* @param {object} params
* @param {import('@sveltejs/kit').Builder} params.builder
* @param {string} params.name
* @param {boolean} params.reroute_middleware
*/
async function bundle_edge_function({ builder, name, reroute_middleware }) {
const tmp = builder.getBuildDirectory('netlify-tmp');

const relativePath = posix.relative(tmp, builder.getServerDirectory());
const manifest = builder.generateManifest({
relativePath,
rerouteMiddleware: reroute_middleware
});
writeFileSync(`${tmp}/manifest.js`, `export const manifest = ${manifest};\n`);

await esbuild.build({
entryPoints: [`${tmp}/entry.js`],
outfile: `.netlify/edge-functions/${name}.js`,
bundle: true,
format: 'esm',
platform: 'browser',
sourcemap: 'linked',
target: 'es2020',
loader: {
'.wasm': 'copy',
'.woff': 'copy',
'.woff2': 'copy',
'.ttf': 'copy',
'.eot': 'copy',
'.otf': 'copy'
},
// Node built-ins are allowed, but must be prefixed with `node:`
// https://docs.netlify.com/edge-functions/api/#runtime-environment
external: builtinModules.map((id) => `node:${id}`),
alias: Object.fromEntries(builtinModules.map((id) => [id, `node:${id}`]))
});

/** @type {{ assets: Set<string> }} */
// we have to prepend the file:// protocol because Windows doesn't support absolute path imports
const { assets } = (await import(`file://${tmp}/manifest.js`)).manifest;

const path = '/*';
// We only need to specify paths without the trailing slash because
// Netlify will handle the optional trailing slash for us
const excludedPath = [
const excluded = [
// Contains static files
`/${builder.getAppPath()}/*`,
...builder.prerendered.paths,
Expand All @@ -179,49 +231,29 @@ async function generate_edge_functions({ builder }) {
'/.netlify/*'
];

/** @type {HandlerManifest} */
/** @type {import('@netlify/edge-functions').Manifest} */
const edge_manifest = {
functions: [
{
function: 'render',
function: name,
path,
excludedPath
excludedPath: /** @type {`/${string}`[]} */ (excluded)
}
],
version: 1
};

await esbuild.build({
entryPoints: [`${tmp}/entry.js`],
outfile: '.netlify/edge-functions/render.js',
bundle: true,
format: 'esm',
platform: 'browser',
sourcemap: 'linked',
target: 'es2020',
loader: {
'.wasm': 'copy',
'.woff': 'copy',
'.woff2': 'copy',
'.ttf': 'copy',
'.eot': 'copy',
'.otf': 'copy'
},
// Node built-ins are allowed, but must be prefixed with `node:`
// https://docs.netlify.com/edge-functions/api/#runtime-environment
external: builtinModules.map((id) => `node:${id}`),
alias: Object.fromEntries(builtinModules.map((id) => [id, `node:${id}`]))
});

writeFileSync('.netlify/edge-functions/manifest.json', JSON.stringify(edge_manifest));
}

/**
* @param { object } params
* @param {object} params
* @param {import('@sveltejs/kit').Builder} params.builder
* @param { string } params.publish
* @param { boolean } params.split
* @param {string} params.publish
* @param {boolean} params.split
* @param {boolean} params.reroute_middleware
*/
function generate_lambda_functions({ builder, publish, split }) {
function generate_lambda_functions({ builder, publish, split, reroute_middleware }) {
builder.mkdirp('.netlify/functions-internal/.svelte-kit');

/** @type {string[]} */
Expand Down Expand Up @@ -282,7 +314,8 @@ function generate_lambda_functions({ builder, publish, split }) {

const manifest = builder.generateManifest({
relativePath: '../server',
routes
routes,
rerouteMiddleware: reroute_middleware
});

const fn = `import { init } from '../serverless.js';\n\nexport const handler = init(${manifest});\n`;
Expand All @@ -296,7 +329,8 @@ function generate_lambda_functions({ builder, publish, split }) {
}
} else {
const manifest = builder.generateManifest({
relativePath: '../server'
relativePath: '../server',
rerouteMiddleware: reroute_middleware
});

const fn = `import { init } from '../serverless.js';\n\nexport const handler = init(${manifest});\n`;
Expand Down
7 changes: 7 additions & 0 deletions packages/adapter-netlify/internal.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,10 @@ declare module 'MANIFEST' {

export const manifest: SSRManifest;
}

declare module '__HOOKS__' {
// eslint-disable-next-line no-duplicate-imports
import { Reroute } from '@sveltejs/kit';

export const reroute: Reroute;
}
10 changes: 7 additions & 3 deletions packages/adapter-netlify/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,19 +34,23 @@
"scripts": {
"dev": "node -e \"fs.rmSync('files', { force: true, recursive: true })\" && rollup -cw",
"build": "node -e \"fs.rmSync('files', { force: true, recursive: true })\" && rollup -c && node -e \"fs.cpSync('src/edge.js', 'files/edge.js')\"",
"test": "vitest run",
"check": "tsc",
"lint": "prettier --check .",
"format": "pnpm lint --write",
"prepublishOnly": "pnpm build"
"prepublishOnly": "pnpm build",
"test": "pnpm test:unit && pnpm test:integration",
"test:unit": "vitest run",
"test:integration": "pnpm build && pnpm -r --workspace-concurrency 1 --filter=\"./test/**\" test"
},
"dependencies": {
"@iarna/toml": "^2.2.5",
"esbuild": "^0.25.4",
"set-cookie-parser": "^2.6.0"
},
"devDependencies": {
"@netlify/edge-functions": "^2.11.1",
"@netlify/functions": "^3.0.0",
"@playwright/test": "^1.44.1",
"@rollup/plugin-commonjs": "^28.0.1",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^16.0.0",
Expand All @@ -59,6 +63,6 @@
"vitest": "^3.1.1"
},
"peerDependencies": {
"@sveltejs/kit": "^2.4.0"
"@sveltejs/kit": "^2.19.0"
}
}
Loading
Loading