Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
99 commits
Select commit Hold shift + click to select a range
6d536c4
wip
teemingc Jun 4, 2024
58db3ce
fix types
teemingc Jun 4, 2024
12c1dc5
use import alias instead of builder.copy replace
teemingc Jun 5, 2024
5682120
add netlify support
teemingc Jun 5, 2024
91aeaaf
oops we only need one edge function for serverless split
teemingc Jun 5, 2024
06337fd
readability
teemingc Jun 5, 2024
7eeb691
Merge branch 'main' into fix-reroute-split
teemingc Jun 6, 2024
25c38a0
cleanup vercel implementation
teemingc Jun 6, 2024
28e7f39
this can be sync
teemingc Jun 6, 2024
766d4f7
make temp file a variable
teemingc Jun 7, 2024
2f40af2
fix node types with @vercel/edge
teemingc Jun 8, 2024
6ccef50
add separate tsconfig for edge files
teemingc Jun 8, 2024
322ba6c
prettier
teemingc Jun 8, 2024
2306806
cleanup netlify
teemingc Jun 9, 2024
5cfc8eb
docs
teemingc Jun 16, 2024
d8c29f3
changeset
teemingc Jun 16, 2024
f28e444
Merge branch 'main' into fix-reroute-split
teemingc Jul 24, 2024
58dfb9d
Update .changeset/hot-guests-enjoy.md
teemingc Oct 10, 2024
2302812
Merge branch 'main' into fix-reroute-split
Oct 10, 2024
2cfc6c6
Merge branch 'main' into fix-reroute-split
Oct 29, 2024
1f9d964
fix broken lockfile
Oct 29, 2024
ab4705e
Merge branch 'main' into fix-reroute-split
Dec 2, 2024
2d82cac
check if reroute hook exists before generating reroute middleware
Dec 2, 2024
2125115
bump @vercel/edge to 1.1.2
Dec 2, 2024
59b5f4b
copy over reroute.js
Dec 2, 2024
dc440c1
how do I get esbuild to bundle vercel/edge?
Dec 2, 2024
1d1a67d
add rollup to bundle @vercel/edge
Dec 3, 2024
c19a4ae
format
Dec 3, 2024
1a88cfa
Merge branch 'main' into fix-reroute-split
Jan 17, 2025
e75d042
Merge branch 'main' into fix-reroute-split
Jan 21, 2025
aa125d5
disable duplicate import eslint rule for line
Jan 21, 2025
014e5dd
bump @vercel/edge
Jan 21, 2025
11c4a70
strip sveltekit url internals before passing url to reroute
Jan 21, 2025
e9a2f85
revert
Jan 21, 2025
a589063
Update .changeset/hot-guests-enjoy.md
teemingc Jan 21, 2025
46ac4df
Update documentation/docs/25-build-and-deploy/90-adapter-vercel.md
teemingc Jan 21, 2025
f965bc0
Merge branch 'main' into fix-reroute-split
Jan 21, 2025
bb7418f
restore original path and export middleware reroute function
Jan 23, 2025
33c763f
Merge branch 'main' into fix-reroute-split
Jan 23, 2025
1da5a79
remove logs
Jan 23, 2025
d13fb42
bump adapter kit peer version
Jan 23, 2025
25a88d0
format
Jan 23, 2025
435e12e
reword changeset
Jan 23, 2025
8d667e6
fix incorrect merge
Jan 23, 2025
95d0d42
format
Jan 23, 2025
4fb32a5
apparently the edge middleware preserves the original url so we don't…
Jan 23, 2025
9934caa
format
Jan 23, 2025
705f2d1
fix merge discrepencies
Jan 23, 2025
a352d7c
Merge branch 'main' into fix-reroute-split
Jan 24, 2025
e38a660
fix endless loop on Netlify
Jan 24, 2025
5783e9b
restore original path
Jan 24, 2025
09a744b
format
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
Feb 5, 2025
826bdd1
kit changeset
Feb 5, 2025
08000bd
Update packages/adapter-netlify/package.json
teemingc Feb 12, 2025
32af931
Update packages/adapter-vercel/package.json
teemingc Feb 12, 2025
0aa33e7
Merge branch 'main' into fix-reroute-split
Feb 20, 2025
a984fff
await setResponse
Feb 20, 2025
849c8ed
it wasn't awaited before so let's not await it
Feb 20, 2025
2ce404d
Merge branch 'main' into fix-reroute-split
Mar 6, 2025
1a29182
skip reroute based on the manifest value
Mar 6, 2025
d31d06f
oops forgot this
Mar 6, 2025
95037c3
fix
Mar 6, 2025
0f997ed
ensure reroute does not run twice even if resolved path is the same
Mar 7, 2025
e038a3b
Merge branch 'main' into fix-reroute-split
Apr 9, 2025
eaa8586
fix doc links
Apr 9, 2025
df46216
avoid rewrite if pathname is the same
Apr 9, 2025
40df48a
check value of reroute export
Apr 9, 2025
d0fd71b
generate types
Apr 9, 2025
b86bfad
better docs
Apr 10, 2025
d73f37f
fix netlify infinite loop
Apr 11, 2025
2ea355c
add netlify test
Apr 11, 2025
509a5d8
format
Apr 11, 2025
2536de0
Merge branch 'main' into fix-reroute-split
Apr 14, 2025
6c99e04
fix lint
Apr 14, 2025
7f226de
Merge branch 'main' into fix-reroute-split
Jun 4, 2025
a5078a9
Merge branch 'main' into fix-reroute-split
Jun 30, 2025
c8c9616
Merge branch 'main' into fix-reroute-split
Jul 11, 2025
679b1af
Merge branch 'main' into fix-reroute-split
teemingc Feb 3, 2026
56478d9
Merge branch 'main' into fix-reroute-split
teemingc Feb 3, 2026
a284cdc
clean up
teemingc Feb 3, 2026
74e9f3c
Merge branch 'fix-reroute-split' of https://github.com/sveltejs/kit i…
teemingc Feb 3, 2026
c0234f5
re-add edge function rewrite
teemingc Feb 3, 2026
70bab35
git ignore
teemingc Feb 3, 2026
b87a0d8
remove unused allowed build dep
teemingc Feb 3, 2026
913b369
fix skip logic
teemingc Feb 3, 2026
41c6dc9
explain
teemingc Feb 3, 2026
8e651b1
remove deprecated context
teemingc Feb 3, 2026
42ea5e5
fix test suite not picking up static dir
teemingc Feb 3, 2026
599016f
format
teemingc Feb 3, 2026
75666f9
Merge branch 'main' into fix-reroute-split
teemingc Feb 3, 2026
664fa98
add doc page
teemingc Feb 3, 2026
bb93a3e
Merge branch 'fix-reroute-split' of https://github.com/sveltejs/kit i…
teemingc Feb 3, 2026
d2e46b6
Merge branch 'main' into fix-reroute-split
teemingc Feb 3, 2026
10f060b
capitalise id
teemingc Feb 3, 2026
e4a7b3b
Merge branch 'fix-reroute-split' of https://github.com/sveltejs/kit i…
teemingc Feb 3, 2026
0ce18bd
Merge branch 'main' into fix-reroute-split
teemingc Feb 4, 2026
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
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 @@ -120,6 +120,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 @@ -189,6 +189,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
5 changes: 5 additions & 0 deletions documentation/docs/98-reference/15-@sveltejs-kit-adapter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
title: @sveltejs/kit/adapter
---

> MODULE: @sveltejs/kit/adapter
4 changes: 2 additions & 2 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export default [
},
ignores: [
'packages/adapter-cloudflare/test/apps/**/*',
'packages/adapter-netlify/test/preview.js',
'packages/adapter-netlify/test/apps/**/*',
'packages/adapter-node/rollup.config.js',
'packages/adapter-node/tests/smoke.spec_disabled.js',
Expand All @@ -60,8 +61,7 @@ export default [
'packages/kit/test/apps/**/*',
'packages/kit/test/build-errors/**/*',
'packages/kit/test/prerendering/**/*',
'packages/test-redirect-importer/index.js',
'packages/adapter-netlify/test/preview.js'
'packages/test-redirect-importer/index.js'
]
}
];
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@
"rolldown",
"sharp",
"svelte-preprocess",
"unix-dgram",
"workerd"
]
}
Expand Down
154 changes: 104 additions & 50 deletions packages/adapter-netlify/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,14 +97,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 @@ -123,18 +133,21 @@ export default function ({ split = false, edge = edge_set_in_env_var } = {}) {
}
};
}

/**
* @param { object } params
* @param {object} params
* @param {Builder2_4_0} 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 @@ -144,49 +157,48 @@ async function generate_edge_functions({ builder }) {
}
});

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

writeFileSync(`${tmp}/manifest.js`, `export const manifest = ${manifest};\n`);
/**
* @param {object} params
* @param {Builder2_4_0} 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...');

/** @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 tmp = builder.getBuildDirectory('netlify-tmp');
builder.rimraf(tmp);
builder.mkdirp(tmp);

const path = '/*';
// We only need to specify paths without the trailing slash because
// Netlify will handle the optional trailing slash for us
const excluded = [
// Contains static files
`/${builder.getAppPath()}/immutable/*`,
`/${builder.getAppPath()}/version.json`,
...builder.prerendered.paths,
...Array.from(assets).flatMap((asset) => {
if (asset.endsWith('/index.html')) {
const dir = asset.replace(/\/index\.html$/, '');
return [
`${builder.config.kit.paths.base}/${asset}`,
`${builder.config.kit.paths.base}/${dir}`
];
}
return `${builder.config.kit.paths.base}/${asset}`;
}),
// Should not be served by SvelteKit at all
'/.netlify/*'
];
builder.mkdirp('.netlify/edge-functions');

/** @type {import('@netlify/edge-functions').Manifest} */
const edge_manifest = {
functions: [
{
function: 'render',
path,
excludedPath: /** @type {`/${string}`[]} */ (excluded)
}
],
version: 1
};
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 {Builder2_4_0} 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`);

/** @type {BuildOptions} */
const esbuild_config = {
Expand All @@ -211,7 +223,7 @@ async function generate_edge_functions({ builder }) {
await Promise.all([
esbuild.build({
entryPoints: [`${tmp}/entry.js`],
outfile: '.netlify/edge-functions/render.js',
outfile: `.netlify/edge-functions/${name}.js`,
...esbuild_config
}),
builder.hasServerInstrumentationFile?.() &&
Expand All @@ -230,15 +242,55 @@ async function generate_edge_functions({ builder }) {
});
}

/** @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;

// We only need to specify paths without the trailing slash because
// Netlify will handle the optional trailing slash for us
const app_path = builder.getAppPath();
const excluded = [
// Contains static files
`/${app_path}/immutable/*`,
`/${app_path}/version.json`,
...builder.prerendered.paths,
...Array.from(assets).flatMap((asset) => {
if (asset.endsWith('/index.html')) {
const dir = asset.replace(/\/index\.html$/, '');
return [
`${builder.config.kit.paths.base}/${asset}`,
`${builder.config.kit.paths.base}/${dir}`
];
}
return `${builder.config.kit.paths.base}/${asset}`;
}),
// Should not be served by SvelteKit at all
'/.netlify/*'
];

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

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

/**
* @param { object } params
* @param {object} params
* @param {Builder2_4_0} 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 @@ -299,7 +351,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 @@ -323,7 +376,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;
}
4 changes: 2 additions & 2 deletions packages/adapter-netlify/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,11 @@
"@sveltejs/vite-plugin-svelte": "catalog:",
"@types/node": "catalog:",
"@types/set-cookie-parser": "catalog:",
"rollup": "^4.14.2",
"rollup": "catalog:",
"typescript": "^5.3.3",
"vitest": "catalog:"
},
"peerDependencies": {
"@sveltejs/kit": "^2.4.0"
"@sveltejs/kit": "^2.51.0"
}
}
7 changes: 5 additions & 2 deletions packages/adapter-netlify/rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import commonjs from '@rollup/plugin-commonjs';
import json from '@rollup/plugin-json';
import { rmSync } from 'node:fs';

const EXTERNAL = new Set(['0SERVER', 'MANIFEST', '__HOOKS__']);

/**
* @param {string} filepath
* @returns {import('rollup').Plugin}
Expand All @@ -25,15 +27,16 @@ const config = {
input: {
serverless: 'src/serverless.js',
shims: 'src/shims.js',
edge: 'src/edge.js'
edge: 'src/edge.js',
reroute: 'src/reroute.js'
},
output: {
dir: 'files',
format: 'esm'
},
// @ts-ignore https://github.com/rollup/plugins/issues/1329
plugins: [clearOutput('files'), nodeResolve({ preferBuiltins: true }), commonjs(), json()],
external: (id) => id === '0SERVER' || id === 'MANIFEST' || id.startsWith('node:'),
external: (id) => EXTERNAL.has(id) || id.startsWith('node:'),
preserveEntrySignatures: 'exports-only'
};

Expand Down
13 changes: 13 additions & 0 deletions packages/adapter-netlify/src/reroute.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { reroute } from '__HOOKS__';
import { applyReroute } from '@sveltejs/kit/adapter';

/** @type {import('@netlify/edge-functions').EdgeFunction} */
export default async function middleware(request, context) {
const resolved_url = await applyReroute(request.url, reroute);

// Netlify rewrites can cause an endless loop because it will re-run this
// function with the rewritten URL. Therefore, we use `context.next` instead
// to specifically invoke the next function in the chain with the rewritten URL
const new_request = new Request(resolved_url, request);
return context.next(new_request);
}
4 changes: 2 additions & 2 deletions packages/adapter-netlify/src/serverless.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export function init(manifest) {
* @param {import('@netlify/functions').HandlerEvent} event
* @returns {Request}
*/
function to_request({ httpMethod, headers, rawUrl, body, isBase64Encoded }) {
function to_request({ httpMethod, headers, rawUrl, rawQuery, body, isBase64Encoded }) {
/** @type {RequestInit} */
const init = {
method: httpMethod,
Expand All @@ -69,7 +69,7 @@ function to_request({ httpMethod, headers, rawUrl, body, isBase64Encoded }) {
init.body = typeof body === 'string' ? Buffer.from(body, encoding) : body;
}

return new Request(rawUrl, init);
return new Request(rawUrl + (rawQuery ? `?${rawQuery}` : ''), init);
}

const text_types = new Set([
Expand Down
2 changes: 1 addition & 1 deletion packages/adapter-netlify/test/apps/basic/netlify.toml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
[dev]
[build]
publish = "build"
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,6 @@ export default async function (request, context) {
}

export const config = {
path: '/*'
path: '/*',
preferStatic: true
};
Loading
Loading