Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
3 changes: 2 additions & 1 deletion docs/1.docs/50.tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ export default defineNitroConfig({
### Platform support

- `dev`, `node-server`, `bun` and `deno-server` presets are supported with [croner](https://croner.56k.guru/) engine.
- `cloudflare_module` preset have native integration with [Cron Triggers](https://developers.cloudflare.com/workers/configuration/cron-triggers/). Make sure to configure wrangler to use exactly same patterns you define in `scheduledTasks` to be matched.
- `cloudflare_module` preset has native integration with [Cron Triggers](https://developers.cloudflare.com/workers/configuration/cron-triggers/). Make sure to configure wrangler to use the same patterns you define in `scheduledTasks` to be matched.
- `vercel` preset has native integration with [Vercel Cron Jobs](https://vercel.com/docs/cron-jobs). Nitro automatically generates the cron job configuration at build time β€” no manual `vercel.json` setup required.
- More presets (with native primitives support) are planned to be supported!

## Programmatically run tasks
Expand Down
28 changes: 28 additions & 0 deletions docs/2.deploy/20.providers/vercel.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,34 @@ When the proxy rule uses any of the following `ProxyOptions`, Nitro keeps it as
Response headers defined on the route rule via the `headers` option are still applied to CDN-level rewrites. Only request-level `ProxyOptions.headers` (sent to the upstream) require a runtime proxy.
::

## Scheduled tasks (Cron Jobs)

:read-more{title="Vercel Cron Jobs" to="https://vercel.com/docs/cron-jobs"}

Nitro automatically converts your [`scheduledTasks`](/docs/tasks#scheduled-tasks) configuration into [Vercel Cron Jobs](https://vercel.com/docs/cron-jobs) at build time. Define your schedules in your Nitro config and deploy - no manual `vercel.json` cron configuration required.

```ts [nitro.config.ts]
import { defineNitroConfig } from "nitro/config";

export default defineNitroConfig({
experimental: {
tasks: true
},
scheduledTasks: {
// Run `cms:update` every hour
'0 * * * *': ['cms:update'],
// Run `db:cleanup` every day at midnight
'0 0 * * *': ['db:cleanup']
}
})
```

### Secure cron job endpoints

:read-more{title="Securing cron jobs" to="https://vercel.com/docs/cron-jobs/manage-cron-jobs#securing-cron-jobs"}

To prevent unauthorized access to the cron handler, set a `CRON_SECRET` environment variable in your Vercel project settings. When `CRON_SECRET` is set, Nitro validates the `Authorization` header on every cron invocation.

## 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
14 changes: 14 additions & 0 deletions src/presets/vercel/preset.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { defineNitroPreset } from "../_utils/preset.ts";
import type { Nitro } from "nitro/types";
import { presetsDir } from "nitro/meta";
import { join } from "pathe";
import {
deprecateSWR,
generateFunctionFiles,
Expand Down Expand Up @@ -50,6 +52,18 @@ const vercel = defineNitroPreset(
}
logger.info(`Using \`${serverFormat}\` entry format.`);
nitro.options.entry = nitro.options.entry.replace("{format}", serverFormat);

// Cron tasks handler
if (
nitro.options.experimental.tasks &&
Object.keys(nitro.options.scheduledTasks || {}).length > 0
) {
nitro.options.handlers.push({
route: "/_nitro/tasks/vercel",
Comment thread
pi0 marked this conversation as resolved.
Outdated
lazy: true,
handler: join(presetsDir, "vercel/runtime/cron-handler"),
});
}
},
"rollup:before": (nitro: Nitro) => {
deprecateSWR(nitro);
Expand Down
27 changes: 27 additions & 0 deletions src/presets/vercel/runtime/cron-handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { H3, HTTPError } from "h3";
import { runCronTasks } from "#nitro/runtime/task";

export default new H3().get("/_nitro/tasks/vercel", async (event) => {
Comment thread
pi0 marked this conversation as resolved.
Outdated
// Validate CRON_SECRET if set - https://vercel.com/docs/cron-jobs/manage-cron-jobs#securing-cron-jobs
const cronSecret = process.env.CRON_SECRET;
if (cronSecret) {
const authHeader = event.req.headers.get("authorization");
if (authHeader !== `Bearer ${cronSecret}`) {
Comment thread
RihanArfan marked this conversation as resolved.
Outdated
throw new HTTPError("Unauthorized", { status: 401 });
}
}

const cron = event.req.headers.get("x-vercel-cron-schedule");
if (!cron) {
throw new HTTPError("Missing x-vercel-cron-schedule header", { status: 400 });
}
Comment thread
RihanArfan marked this conversation as resolved.

const result = await runCronTasks(cron, {
context: {},
payload: {
scheduledTime: Date.now(),
},
});

return { cron, tasks: result };

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this object consumed by vercel? Why this specific shape?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No particular reason. I've double checked and Vercel only shows things that were logged but not the response of this endpoint, so I'll update it so we return nothing. Should we log anything? Like Running cron schedule * * * * *

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We mighjt like success: true for example. My main concern is to not introduce a reponse and break it later only

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorted

});
12 changes: 12 additions & 0 deletions src/presets/vercel/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,18 @@ function generateBuildConfig(nitro: Nitro, o11Routes?: ObservabilityRoute[]) {
],
} as VercelBuildConfigV3);

// Cron jobs from scheduledTasks
if (
nitro.options.experimental.tasks &&
Object.keys(nitro.options.scheduledTasks || {}).length > 0
) {
const cronEntries = Object.keys(nitro.options.scheduledTasks).map((schedule) => ({
path: "/_nitro/tasks/vercel",
schedule,
}));
config.crons = [...cronEntries, ...(config.crons || [])];
}

// Early return if we are building a static site
if (nitro.options.static) {
return config;
Expand Down
Loading