Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
6 changes: 6 additions & 0 deletions docs/2.deploy/20.providers/vercel.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,12 @@ export default defineEventHandler(async (event) => {
});
```

### Local development

Queues work in `nitro dev` β€” `send()` delivers messages straight to your `vercel:queue` hook, so you can iterate without deploying. Pull your Vercel environment first with `vercel link` and `vercel env pull` so the SDK can authenticate.

If your hook throws, the message is retried locally. Retries honour `retryAfterSeconds` from each trigger when set.

## Custom build output configuration

You can provide additional [build output configuration](https://vercel.com/docs/build-output-api/v3) using `vercel.config` key inside `nitro.config`. It will be merged with built-in auto-generated config.
Expand Down
4 changes: 2 additions & 2 deletions src/presets/_types.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,6 @@ export interface PresetOptions {

export const presetsWithConfig = ["awsAmplify","awsLambda","azure","cloudflare","firebase","netlify","vercel","zephyr"] as const;

export type PresetName = "alwaysdata" | "aws-amplify" | "aws-lambda" | "azure-swa" | "base-worker" | "bun" | "cleavr" | "cloudflare-dev" | "cloudflare-durable" | "cloudflare-module" | "cloudflare-pages" | "cloudflare-pages-static" | "deno" | "deno-deploy" | "deno-server" | "digital-ocean" | "edgeone" | "edgeone-pages" | "firebase-app-hosting" | "flight-control" | "genezio" | "github-pages" | "gitlab-pages" | "heroku" | "iis-handler" | "iis-node" | "koyeb" | "netlify" | "netlify-edge" | "netlify-static" | "nitro-dev" | "nitro-prerender" | "node" | "node-cluster" | "node-middleware" | "node-server" | "platform-sh" | "render-com" | "standard" | "static" | "stormkit" | "vercel" | "vercel-static" | "winterjs" | "zeabur" | "zeabur-static" | "zephyr" | "zerops" | "zerops-static";
export type PresetName = "alwaysdata" | "aws-amplify" | "aws-lambda" | "azure-swa" | "base-worker" | "bun" | "cleavr" | "cloudflare-dev" | "cloudflare-durable" | "cloudflare-module" | "cloudflare-pages" | "cloudflare-pages-static" | "deno" | "deno-deploy" | "deno-server" | "digital-ocean" | "edgeone" | "edgeone-pages" | "firebase-app-hosting" | "flight-control" | "genezio" | "github-pages" | "gitlab-pages" | "heroku" | "iis-handler" | "iis-node" | "koyeb" | "netlify" | "netlify-edge" | "netlify-static" | "nitro-dev" | "nitro-prerender" | "node" | "node-cluster" | "node-middleware" | "node-server" | "platform-sh" | "render-com" | "standard" | "static" | "stormkit" | "vercel" | "vercel-dev" | "vercel-static" | "winterjs" | "zeabur" | "zeabur-static" | "zephyr" | "zerops" | "zerops-static";

export type PresetNameInput = "alwaysdata" | "aws-amplify" | "awsAmplify" | "aws_amplify" | "aws-lambda" | "awsLambda" | "aws_lambda" | "azure-swa" | "azureSwa" | "azure_swa" | "base-worker" | "baseWorker" | "base_worker" | "bun" | "cleavr" | "cloudflare-dev" | "cloudflareDev" | "cloudflare_dev" | "cloudflare-durable" | "cloudflareDurable" | "cloudflare_durable" | "cloudflare-module" | "cloudflareModule" | "cloudflare_module" | "cloudflare-pages" | "cloudflarePages" | "cloudflare_pages" | "cloudflare-pages-static" | "cloudflarePagesStatic" | "cloudflare_pages_static" | "deno" | "deno-deploy" | "denoDeploy" | "deno_deploy" | "deno-server" | "denoServer" | "deno_server" | "digital-ocean" | "digitalOcean" | "digital_ocean" | "edgeone" | "edgeone-pages" | "edgeonePages" | "edgeone_pages" | "firebase-app-hosting" | "firebaseAppHosting" | "firebase_app_hosting" | "flight-control" | "flightControl" | "flight_control" | "genezio" | "github-pages" | "githubPages" | "github_pages" | "gitlab-pages" | "gitlabPages" | "gitlab_pages" | "heroku" | "iis-handler" | "iisHandler" | "iis_handler" | "iis-node" | "iisNode" | "iis_node" | "koyeb" | "netlify" | "netlify-edge" | "netlifyEdge" | "netlify_edge" | "netlify-static" | "netlifyStatic" | "netlify_static" | "nitro-dev" | "nitroDev" | "nitro_dev" | "nitro-prerender" | "nitroPrerender" | "nitro_prerender" | "node" | "node-cluster" | "nodeCluster" | "node_cluster" | "node-middleware" | "nodeMiddleware" | "node_middleware" | "node-server" | "nodeServer" | "node_server" | "platform-sh" | "platformSh" | "platform_sh" | "render-com" | "renderCom" | "render_com" | "standard" | "static" | "stormkit" | "vercel" | "vercel-static" | "vercelStatic" | "vercel_static" | "winterjs" | "zeabur" | "zeabur-static" | "zeaburStatic" | "zeabur_static" | "zephyr" | "zerops" | "zerops-static" | "zeropsStatic" | "zerops_static" | (string & {});
export type PresetNameInput = "alwaysdata" | "aws-amplify" | "awsAmplify" | "aws_amplify" | "aws-lambda" | "awsLambda" | "aws_lambda" | "azure-swa" | "azureSwa" | "azure_swa" | "base-worker" | "baseWorker" | "base_worker" | "bun" | "cleavr" | "cloudflare-dev" | "cloudflareDev" | "cloudflare_dev" | "cloudflare-durable" | "cloudflareDurable" | "cloudflare_durable" | "cloudflare-module" | "cloudflareModule" | "cloudflare_module" | "cloudflare-pages" | "cloudflarePages" | "cloudflare_pages" | "cloudflare-pages-static" | "cloudflarePagesStatic" | "cloudflare_pages_static" | "deno" | "deno-deploy" | "denoDeploy" | "deno_deploy" | "deno-server" | "denoServer" | "deno_server" | "digital-ocean" | "digitalOcean" | "digital_ocean" | "edgeone" | "edgeone-pages" | "edgeonePages" | "edgeone_pages" | "firebase-app-hosting" | "firebaseAppHosting" | "firebase_app_hosting" | "flight-control" | "flightControl" | "flight_control" | "genezio" | "github-pages" | "githubPages" | "github_pages" | "gitlab-pages" | "gitlabPages" | "gitlab_pages" | "heroku" | "iis-handler" | "iisHandler" | "iis_handler" | "iis-node" | "iisNode" | "iis_node" | "koyeb" | "netlify" | "netlify-edge" | "netlifyEdge" | "netlify_edge" | "netlify-static" | "netlifyStatic" | "netlify_static" | "nitro-dev" | "nitroDev" | "nitro_dev" | "nitro-prerender" | "nitroPrerender" | "nitro_prerender" | "node" | "node-cluster" | "nodeCluster" | "node_cluster" | "node-middleware" | "nodeMiddleware" | "node_middleware" | "node-server" | "nodeServer" | "node_server" | "platform-sh" | "platformSh" | "platform_sh" | "render-com" | "renderCom" | "render_com" | "standard" | "static" | "stormkit" | "vercel" | "vercel-dev" | "vercelDev" | "vercel_dev" | "vercel-static" | "vercelStatic" | "vercel_static" | "winterjs" | "zeabur" | "zeabur-static" | "zeaburStatic" | "zeabur_static" | "zephyr" | "zerops" | "zerops-static" | "zeropsStatic" | "zerops_static" | (string & {});
44 changes: 44 additions & 0 deletions src/presets/vercel/dev.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import type { Nitro } from "nitro/types";
import { presetsDir } from "nitro/meta";
import { resolveModulePath } from "exsolve";

/**
* Configure local development emulation for the Vercel preset.
*
* When `vercel.queues.triggers` is configured, propagates the trigger list
* to runtime config and injects a runtime plugin that binds each topic to
* the `vercel:queue` hook through env-runner's queue dev bridge.
*
*/
export async function vercelDevModule(nitro: Nitro) {
if (!nitro.options.dev) {
return;
}

const triggers = nitro.options.vercel?.queues?.triggers;
if (!triggers?.length) {
return;
}

if (nitro.options.devServer.runner !== "vercel") {
throw new Error(
`[vercel:queue] Local queue delivery requires the \`vercel\` dev runner, but \`devServer.runner\` is set to "${nitro.options.devServer.runner}". Remove the \`devServer.runner\` override in your \`nitro.config.ts\` or set it explicitly to \`"vercel"\`.`
);
}

// Propagate triggers to the runtime plugin via runtimeConfig.
nitro.options.runtimeConfig.vercel = {
...nitro.options.runtimeConfig.vercel,
queues: {
triggers: triggers.map((t) => ({ ...t })),
},
};

nitro.options.plugins = nitro.options.plugins || [];
nitro.options.plugins.unshift(
resolveModulePath("./vercel/runtime/queue.dev", {
from: presetsDir,
extensions: [".mjs", ".ts"],
})
);
}
16 changes: 15 additions & 1 deletion src/presets/vercel/preset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
generateStaticFiles,
resolveVercelRuntime,
} from "./utils.ts";
import { vercelDevModule } from "./dev.ts";

import type { VercelFunctionTrigger } from "./types.ts";

Expand Down Expand Up @@ -144,4 +145,17 @@ const vercelStatic = defineNitroPreset(
}
);

export default [vercel, vercelStatic] as const;
export const vercelDev = defineNitroPreset(
{
extends: "nitro-dev",
devServer: { runner: "vercel" },
modules: [vercelDevModule],
},
{
name: "vercel-dev" as const,
aliases: ["vercel"],
dev: true,
}
);

export default [vercel, vercelStatic, vercelDev] as const;
57 changes: 57 additions & 0 deletions src/presets/vercel/runtime/queue.dev.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { send } from "@vercel/queue";
import type { MessageMetadata } from "@vercel/queue";
import type { NitroAppPlugin } from "nitro/types";
import { useRuntimeConfig } from "nitro/runtime-config";
import { registerVercelQueueConsumer } from "env-runner/runners/vercel/queue-dev";

Check failure on line 5 in src/presets/vercel/runtime/queue.dev.ts

View workflow job for this annotation

GitHub Actions / tests-checks (ubuntu-latest)

Cannot find module 'env-runner/runners/vercel/queue-dev' or its corresponding type declarations.
Comment thread
pi0 marked this conversation as resolved.

interface DevTrigger {
topic: string;
retryAfterSeconds?: number;
initialDelaySeconds?: number;
}

const queueDevPlugin: NitroAppPlugin = (nitroApp) => {
const triggers =
(useRuntimeConfig() as { vercel?: { queues?: { triggers?: DevTrigger[] } } }).vercel?.queues
?.triggers || [];

if (triggers.length === 0) {
return;
}

const unregisters: Array<() => void> = [];

for (const trigger of triggers) {
const unregister = registerVercelQueueConsumer({
topic: trigger.topic,
retryAfterSeconds: trigger.retryAfterSeconds,
handler: async (message: unknown, metadata: unknown) => {
try {
await nitroApp.hooks.callHook("vercel:queue", {
message,
metadata: metadata as MessageMetadata,
send,
});
} catch (error) {
console.error("[vercel:queue]", error);
Comment thread
pi0 marked this conversation as resolved.
Outdated
nitroApp.captureError?.(error as Error, {
tags: ["vercel:queue"],
});
// Rethrow so @vercel/queue schedules a local retry.
throw error;
}
},
});
unregisters.push(unregister);
}

nitroApp.hooks.hook("close", () => {
for (const unregister of unregisters) {
try {
unregister();
} catch {}
}
});
};

export default queueDevPlugin;
Loading