diff --git a/.changeset/slimy-tips-shake.md b/.changeset/slimy-tips-shake.md
new file mode 100644
index 000000000000..0941c0cf62cd
--- /dev/null
+++ b/.changeset/slimy-tips-shake.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/adapter-vercel': minor
+---
+
+feat: expose `waitUntil` also for serverless runtime
diff --git a/documentation/docs/25-build-and-deploy/90-adapter-vercel.md b/documentation/docs/25-build-and-deploy/90-adapter-vercel.md
index 3251ef137b3b..255e87dabea4 100644
--- a/documentation/docs/25-build-and-deploy/90-adapter-vercel.md
+++ b/documentation/docs/25-build-and-deploy/90-adapter-vercel.md
@@ -177,6 +177,12 @@ Cookie-based skew protection comes with one caveat: if a user has multiple versi
## Notes
+### Platform-specific context
+
+The [`event.platform`](https://svelte.dev/docs/kit/adapters#Platform-specific-context) property is available in server load functions and endpoints and exposes the [`waitUntil`](https://vercel.com/docs/functions/functions-api-reference/vercel-functions-package#waituntil) function.
+
+Use the package [`@vercel/functions`](https://vercel.com/docs/functions/functions-api-reference/vercel-functions-package) for more utilities.
+
### Vercel functions
If you have Vercel functions contained in the `api` directory at the project's root, any requests for `/api/*` will _not_ be handled by SvelteKit. You should implement these as [API routes](routing#server) in your SvelteKit app instead, unless you need to use a non-JavaScript language in which case you will need to ensure that you don't have any `/api/*` routes in your SvelteKit app.
diff --git a/packages/adapter-vercel/ambient.d.ts b/packages/adapter-vercel/ambient.d.ts
index a106f64e3f12..f76bb8b60a83 100644
--- a/packages/adapter-vercel/ambient.d.ts
+++ b/packages/adapter-vercel/ambient.d.ts
@@ -1,12 +1,53 @@
-import { RequestContext } from './index.js';
+import type { waitUntil } from '@vercel/functions';
declare global {
namespace App {
export interface Platform {
/**
* `context` is only available in Edge Functions
+ *
+ * @deprecated Vercel's context is deprecated. Use top-level properties instead.
*/
- context?: RequestContext;
+ context?: {
+ /**
+ * `context` is only available in Edge Functions
+ *
+ * @deprecated Use `event.platform.waitUntil` instead.
+ */
+ waitUntil: typeof waitUntil;
+ };
+
+ /**
+ * A method that can be used to keep the function running after a response has been sent.
+ *
+ * This is useful when you have an async task that you want to keep running even after the
+ * response has been sent and the request has ended.
+ *
+ * The maximum duration depends on your plan and settings (see [here](https://vercel.com/docs/functions/limitations)).
+ *
+ * See https://vercel.com/docs/functions/functions-api-reference/vercel-functions-package#waituntil.
+ *
+ * @example
+ *
+ *
Perform a long-running task in the background without blocking the sending of the response
+ *
+ * ```ts
+ * // src/routes/+page.server.ts
+ *
+ * export const load = async (event) => {
+ * event.platform.waitUntil(longRunningTask())
+ *
+ * return {
+ * // ...some data
+ * }
+ * }
+ *
+ * async function longRunningTask() {
+ * // ...
+ * }
+ * ```
+ */
+ waitUntil: typeof waitUntil;
}
}
}
diff --git a/packages/adapter-vercel/files/edge.js b/packages/adapter-vercel/files/edge.js
index f87bb6e64a98..bbdbf1986ab0 100644
--- a/packages/adapter-vercel/files/edge.js
+++ b/packages/adapter-vercel/files/edge.js
@@ -2,6 +2,7 @@
Vercel Edge Runtime does not support node:process */
import { Server } from 'SERVER';
import { manifest } from 'MANIFEST';
+import { waitUntil } from '@vercel/functions';
const server = new Server(manifest);
@@ -49,9 +50,8 @@ const initialized = server.init({
/**
* @param {Request} request
- * @param {import('../index.js').RequestContext} context
*/
-export default async (request, context) => {
+export default async (request) => {
if (!origin) {
origin = new URL(request.url).origin;
await initialized;
@@ -62,7 +62,10 @@ export default async (request, context) => {
return /** @type {string} */ (request.headers.get('x-forwarded-for'));
},
platform: {
- context
+ context: {
+ waitUntil
+ },
+ waitUntil
}
});
};
diff --git a/packages/adapter-vercel/files/serverless.js b/packages/adapter-vercel/files/serverless.js
index ea16823574f0..18d4e3a4d0d8 100644
--- a/packages/adapter-vercel/files/serverless.js
+++ b/packages/adapter-vercel/files/serverless.js
@@ -3,6 +3,7 @@ import { createReadableStream } from '@sveltejs/kit/node';
import { Server } from 'SERVER';
import { manifest } from 'MANIFEST';
import process from 'node:process';
+import { waitUntil } from '@vercel/functions';
installPolyfills();
@@ -39,6 +40,12 @@ export default {
return server.respond(request, {
getClientAddress() {
return /** @type {string} */ (request.headers.get('x-forwarded-for'));
+ },
+ platform: {
+ context: {
+ waitUntil
+ },
+ waitUntil
}
});
}
diff --git a/packages/adapter-vercel/index.d.ts b/packages/adapter-vercel/index.d.ts
index 49d43085763c..823e14d8f378 100644
--- a/packages/adapter-vercel/index.d.ts
+++ b/packages/adapter-vercel/index.d.ts
@@ -103,59 +103,3 @@ export type Config = (EdgeConfig | ServerlessConfig) & {
*/
images?: ImagesConfig;
};
-
-// we copy the RequestContext interface from `@vercel/edge` because that package can't co-exist with `@types/node`.
-// see https://github.com/sveltejs/kit/pull/9280#issuecomment-1452110035
-
-/**
- * An extension to the standard `Request` object that is passed to every Edge Function.
- *
- * @example
- * ```ts
- * import type { RequestContext } from '@vercel/edge';
- *
- * export default async function handler(request: Request, ctx: RequestContext): Promise {
- * // ctx is the RequestContext
- * }
- * ```
- */
-export interface RequestContext {
- /**
- * A method that can be used to keep the function running after a response has been sent.
- * This is useful when you have an async task that you want to keep running even after the
- * response has been sent and the request has ended.
- *
- * @example
- *
- * Sending an internal error to an error tracking service
- *
- * ```ts
- * import type { RequestContext } from '@vercel/edge';
- *
- * export async function handleRequest(request: Request, ctx: RequestContext): Promise {
- * try {
- * return await myFunctionThatReturnsResponse();
- * } catch (e) {
- * ctx.waitUntil((async () => {
- * // report this error to your error tracking service
- * await fetch('https://my-error-tracking-service.com', {
- * method: 'POST',
- * body: JSON.stringify({
- * stack: e.stack,
- * message: e.message,
- * name: e.name,
- * url: request.url,
- * }),
- * });
- * })());
- * return new Response('Internal Server Error', { status: 500 });
- * }
- * }
- * ```
- */
- waitUntil(
- /**
- * A promise that will be kept alive until it resolves or rejects.
- */ promise: Promise
- ): void;
-}
diff --git a/packages/adapter-vercel/package.json b/packages/adapter-vercel/package.json
index f2de210def6c..916fe4a9fbc8 100644
--- a/packages/adapter-vercel/package.json
+++ b/packages/adapter-vercel/package.json
@@ -40,6 +40,7 @@
"test": "vitest run"
},
"dependencies": {
+ "@vercel/functions": "^3.1.3",
"@vercel/nft": "^0.30.0",
"esbuild": "^0.25.4"
},
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 74004538fd1d..cccdca992145 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -434,6 +434,9 @@ importers:
packages/adapter-vercel:
dependencies:
+ '@vercel/functions':
+ specifier: ^3.1.3
+ version: 3.1.3
'@vercel/nft':
specifier: ^0.30.0
version: 0.30.0(rollup@4.50.1)
@@ -3216,6 +3219,15 @@ packages:
resolution: {integrity: sha512-T+S1KqRD4sg/bHfLwrpF/K3gQLBM1n7Rp7OjjikjTEssI2YJzQpi5WXoynOaQ93ERIuq3O8RBTOUYDKszUCEHw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ '@vercel/functions@3.1.3':
+ resolution: {integrity: sha512-dGYDSOfzjr5ZLfQ2bb4f+2NySsTLX4d6xnUr2Ckc5q/TrPTSJPLjzRzYLQAYO3/aeOv7evSYvPhijURmocu0UQ==}
+ engines: {node: '>= 20'}
+ peerDependencies:
+ '@aws-sdk/credential-provider-web-identity': '*'
+ peerDependenciesMeta:
+ '@aws-sdk/credential-provider-web-identity':
+ optional: true
+
'@vercel/nft@0.29.4':
resolution: {integrity: sha512-6lLqMNX3TuycBPABycx7A9F1bHQR7kiQln6abjFbPrf5C/05qHM9M5E4PeTE59c7z8g6vHnx1Ioihb2AQl7BTA==}
engines: {node: '>=18'}
@@ -3226,6 +3238,10 @@ packages:
engines: {node: '>=18'}
hasBin: true
+ '@vercel/oidc@3.0.2':
+ resolution: {integrity: sha512-JekxQ0RApo4gS4un/iMGsIL1/k4KUBe3HmnGcDvzHuFBdQdudEJgTqcsJC7y6Ul4Yw5CeykgvQbX2XeEJd0+DA==}
+ engines: {node: '>= 20'}
+
'@vitest/browser@3.2.4':
resolution: {integrity: sha512-tJxiPrWmzH8a+w9nLKlQMzAKX/7VjFs50MWgcAj7p9XQ7AQ9/35fByFYptgPELyLw+0aixTnC4pUWV+APcZ/kw==}
peerDependencies:
@@ -9108,6 +9124,10 @@ snapshots:
'@typescript-eslint/types': 8.43.0
eslint-visitor-keys: 4.2.1
+ '@vercel/functions@3.1.3':
+ dependencies:
+ '@vercel/oidc': 3.0.2
+
'@vercel/nft@0.29.4(rollup@4.50.1)(supports-color@10.0.0)':
dependencies:
'@mapbox/node-pre-gyp': 2.0.0(supports-color@10.0.0)
@@ -9146,6 +9166,8 @@ snapshots:
- rollup
- supports-color
+ '@vercel/oidc@3.0.2': {}
+
'@vitest/browser@3.2.4(playwright@1.51.1)(vite@6.3.6(@types/node@18.19.119)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0))(vitest@3.2.4)':
dependencies:
'@testing-library/dom': 10.4.1