Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions docs/2.deploy/20.providers/vercel.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ Nitro checks these sources, in order, to pick the runtime of the server function
3. `bunVersion` in `vercel.json`.
4. The runtime running the build: Bun when building with Bun, otherwise the build's Node.js major version, clamped to the versions Vercel supports.

Vercel supports Node.js 22, 24, and 26. A build on Node.js 23 or 25 uses the next supported version, and a build on a newer version uses `26`.

Steps 2 and 3 follow the same precedence as Vercel.

```json [package.json]
Expand Down
2 changes: 1 addition & 1 deletion src/presets/vercel/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ export interface VercelServerlessFunctionConfig {
/**
* The runtime to use. Defaults to `engines` in `package.json`, then `bunVersion` in `vercel.json`, then the runtime used to build.
*/
runtime?: "nodejs22.x" | "nodejs24.x" | "bun1.x" | "bun1.4.x" | (string & {});
runtime?: "nodejs22.x" | "nodejs24.x" | "nodejs26.x" | "bun1.x" | "bun1.4.x" | (string & {});

/**
* Experimental trigger configuration (e.g., Vercel Queues).
Expand Down
3 changes: 2 additions & 1 deletion src/presets/vercel/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ import { ISR_URL_PARAM } from "./runtime/isr.ts";
// https://vercel.com/docs/build-output-api/configuration

// https://vercel.com/docs/functions/runtimes/node-js/node-js-versions
const SUPPORTED_NODE_VERSIONS = [22, 24];
// https://github.com/vercel/vercel/blob/main/packages/build-utils/src/fs/node-version.ts
const SUPPORTED_NODE_VERSIONS = [22, 24, 26];

// h3 ProxyOptions that Vercel CDN rewrites cannot handle at the edge.
// https://vercel.com/docs/rewrites
Expand Down
47 changes: 44 additions & 3 deletions test/unit/vercel-runtime.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ describe("vercel: resolveVercelRuntime", () => {
beforeEach(async () => {
rootDir = await realpath(await mkdtemp(join(tmpdir(), "nitro-vercel-runtime-")));
warn = vi.fn();
stubNodeVersion("26.0.0");
});

afterEach(async () => {
vi.unstubAllGlobals();
restoreNodeVersion();
await rm(rootDir, { recursive: true, force: true });
});

Expand Down Expand Up @@ -51,9 +53,10 @@ describe("vercel: resolveVercelRuntime", () => {
["22.x", "nodejs22.x"],
["^22", "nodejs22.x"],
[">=20 <24", "nodejs22.x"],
[">=22.0.0", "nodejs24.x"],
["26.x", "nodejs26.x"],
[">=22.0.0", "nodejs26.x"],
[">=24.20.0 <25", "nodejs24.x"],
["^20.19.0 || >=22.12.0", "nodejs24.x"],
["^20.19.0 || >=22.12.0", "nodejs26.x"],
])(
"uses the newest supported Node.js version matching engines.node %s",
async (node, expected) => {
Expand All @@ -62,6 +65,34 @@ describe("vercel: resolveVercelRuntime", () => {
}
);

it.skipIf(isBun).each([
["26.0.0", "nodejs26.x"],
["24.1.0", "nodejs24.x"],
["22.0.0", "nodejs22.x"],
["20.0.0", "nodejs22.x"],
["23.0.0", "nodejs24.x"],
["25.0.0", "nodejs26.x"],
["27.0.0", "nodejs26.x"],
["30.2.1", "nodejs26.x"],
])("uses the runtime for the build Node.js version %s", async (version, expected) => {
stubNodeVersion(version);
expect(await resolveRuntime()).toBe(expected);
expect(warn).not.toHaveBeenCalled();
});

it("accepts an explicit nodejs26.x runtime", async () => {
stubNodeVersion("24.0.0");
expect(await resolveRuntime({ runtime: "nodejs26.x" })).toBe("nodejs26.x");
});

it("does not select versions newer than 26 when building on them", async () => {
stubNodeVersion("28.0.0");
expect(await resolveRuntime({ engines: { node: ">=22" } })).toBe("nodejs26.x");
expect(warn).not.toHaveBeenCalled();
expect(await resolveRuntime({ engines: { node: "28.x" } })).toBe("nodejs26.x");
expect(warn).toHaveBeenCalledWith(expect.stringContaining("(22, 24, 26)"));
});

it("uses engines.node when building with Bun", async () => {
stubBun();
expect(await resolveRuntime({ engines: { node: ">=24.20.0 <25" } })).toBe("nodejs24.x");
Expand Down Expand Up @@ -128,7 +159,7 @@ describe("vercel: resolveVercelRuntime", () => {
expect(await resolveRuntime()).toBe("bun1.x");
});

it.each(["20.x", "18.x", ">=26", "lts/*"])(
it.each(["20.x", "18.x", ">=28", "lts/*"])(
"warns and uses the build Node.js version when engines.node %s matches no supported version",
async (node) => {
const fallback = await resolveRuntime({ engines: { node } });
Expand Down Expand Up @@ -193,6 +224,16 @@ describe("vercel preset: function runtime from engines", () => {
});
});

const originalNodeVersion = Object.getOwnPropertyDescriptor(process.versions, "node")!;

function stubNodeVersion(version: string) {
Object.defineProperty(process.versions, "node", { ...originalNodeVersion, value: version });
}

function restoreNodeVersion() {
Object.defineProperty(process.versions, "node", originalNodeVersion);
}

// `Bun` is a non-configurable global on Bun
function stubBun() {
if (!isBun) {
Expand Down
Loading