Inngest + Next.js Patterns
Templates and conventions for building event-driven, durable background work on Inngest from a Next.js App Router project. The skill enforces the conventions Inngest's own docs use (decentralized eventType() schemas, kebab-case stable function IDs, idempotent steps) so that retries, replays, and concurrency controls actually do what they advertise.
When to Apply
Read this skill when you are:
- Setting up Inngest in a Next.js app for the first time (client + route handler + first function)
- Adding a new background job, webhook handler, or scheduled task to a Next.js app
- Defining a new domain event that other functions will trigger on
- Orchestrating multi-step work that must survive process restarts (durable workflows, sagas)
- Reviewing existing Inngest code for event naming, step idempotency, or stable function IDs
- Migrating from
setTimeout / a queue library / a cron service to Inngest's durable execution model
How to Use
- First time in this codebase? Read
references/conventions.md before writing anything. The conventions explain why event names take a specific shape, why function IDs must be stable, and why every step.run must be idempotent — once you understand the reasoning, the templates make sense.
- Pick a template from the catalog below based on what you're creating.
- Render it by substituting the documented parameters. Each template has a parameter table at the top of its file.
- Wire it up: new functions must be added to
src/inngest/functions/index.ts (the registry the route handler reads). The conventions doc shows the registry pattern.
Available Templates
| Template |
Output Path |
Use When |
client.ts.template |
src/inngest/client.ts |
One-time setup: create the singleton Inngest client. Generate once per app. |
route-handler.ts.template |
src/app/api/inngest/route.ts |
One-time setup: the App Router endpoint Inngest hits to invoke your functions. Generate once per app. |
event.ts.template |
src/inngest/events/{domain}.ts |
Defining a new typed event (with Zod schema) that producers send and functions consume. |
function.ts.template |
src/inngest/functions/{name}.ts |
Standard event-triggered durable function with steps. Parameterized retries, concurrency, throttle, debounce, cancelOn, onFailure. |
function-cron.ts.template |
src/inngest/functions/{name}.ts |
Scheduled function — runs on a cron expression (with timezone). Optionally also accepts a manual event trigger for ad-hoc invocation. |
function-fan-out.ts.template |
src/inngest/functions/{name}.ts |
Orchestrator that receives one event and fans out work — via step.sendEvent (fire-and-forget) or Promise.all(step.invoke(...)) (wait for results). |
Quick Reference
- Event name shape:
domain/entity.verb_past — e.g., app/user.created, shop/order.placed, billing/invoice.paid. The conventions doc has the full rule.
- Function
id: stable kebab-case, never rename it after deploy — Inngest uses it as the durable state key.
- Step
id: descriptive kebab-case, also durable; changing one re-runs that step on replay.
- Idempotency: every
step.run must be safely retryable. If it calls a third-party API that mutates state, pass an idempotency key.
- Dev server:
npx inngest-cli@latest dev, then UI at http://localhost:8288. Set INNGEST_DEV=1 in .env.local to point the SDK at it.
Project Layout Assumption
Templates assume a src/ directory with App Router:
src/
├── app/api/inngest/route.ts # generated by route-handler template
└── inngest/
├── client.ts # generated by client template
├── events/ # one file per domain (events.shop.ts, events.user.ts)
│ └── {domain}.ts # generated by event template
└── functions/
├── index.ts # registry: re-exports all functions as an array
└── {name}.ts # generated by function/cron/fan-out templates
If your project uses a different layout (no src/, or Pages Router), override the output paths in config.json. The conventions doc explains the trade-offs.
Setup
config.json controls path overrides. On first use, check whether its fields are filled in — if not, ask the user before generating (project root differs from the default assumption).
Related Skills
vercel:ai-sdk — if your Inngest functions invoke LLMs, the AI SDK gives you streaming + provider abstraction; pair them with step.run for retry safety.
vercel:vercel-functions — Inngest's serve() adapter for Next.js runs as a Vercel Serverless Function by default; the AI SDK skill has notes on Fluid Compute and timeout extension for long agent runs.
1---
2name: inngest-nextjs-patterns
3description: Inngest event-driven functions in a Next.js (App Router) project — creating the client, the /api/inngest route handler, typed events with Zod schemas, durable event-triggered functions, scheduled cron functions, or fan-out orchestrators. Trigger when scaffolding Inngest patterns — and even if the user just says "add a background job", "process this async", "schedule X every hour", or "send an event when Y happens" in a Next.js codebase. Also trigger when reviewing existing Inngest code to enforce event-naming, function-id stability, and step-idempotency conventions.
4---
5
6# Inngest + Next.js Patterns
7
8Templates and conventions for building event-driven, durable background work on Inngest from a Next.js App Router project. The skill enforces the conventions Inngest's own docs use (decentralized `eventType()` schemas, kebab-case stable function IDs, idempotent steps) so that retries, replays, and concurrency controls actually do what they advertise.
9
10## When to Apply
11
12Read this skill when you are:
13
14- Setting up Inngest in a Next.js app for the first time (client + route handler + first function)
15- Adding a new background job, webhook handler, or scheduled task to a Next.js app
16- Defining a new domain event that other functions will trigger on
17- Orchestrating multi-step work that must survive process restarts (durable workflows, sagas)
18- Reviewing existing Inngest code for event naming, step idempotency, or stable function IDs
19- Migrating from `setTimeout` / a queue library / a cron service to Inngest's durable execution model
20
21## How to Use
22
231. **First time in this codebase?** Read [`references/conventions.md`](references/conventions.md) before writing anything. The conventions explain *why* event names take a specific shape, why function IDs must be stable, and why every `step.run` must be idempotent — once you understand the reasoning, the templates make sense.
242. **Pick a template** from the catalog below based on what you're creating.
253. **Render it** by substituting the documented parameters. Each template has a parameter table at the top of its file.
264. **Wire it up:** new functions must be added to `src/inngest/functions/index.ts` (the registry the route handler reads). The conventions doc shows the registry pattern.
27
28## Available Templates
29
30| Template | Output Path | Use When |
31|---|---|---|
32| [`client.ts.template`](assets/templates/client.ts.template) | `src/inngest/client.ts` | One-time setup: create the singleton Inngest client. Generate **once per app**. |
33| [`route-handler.ts.template`](assets/templates/route-handler.ts.template) | `src/app/api/inngest/route.ts` | One-time setup: the App Router endpoint Inngest hits to invoke your functions. Generate **once per app**. |
34| [`event.ts.template`](assets/templates/event.ts.template) | `src/inngest/events/{domain}.ts` | Defining a new typed event (with Zod schema) that producers send and functions consume. |
35| [`function.ts.template`](assets/templates/function.ts.template) | `src/inngest/functions/{name}.ts` | Standard event-triggered durable function with steps. Parameterized retries, concurrency, throttle, debounce, cancelOn, onFailure. |
36| [`function-cron.ts.template`](assets/templates/function-cron.ts.template) | `src/inngest/functions/{name}.ts` | Scheduled function — runs on a cron expression (with timezone). Optionally also accepts a manual event trigger for ad-hoc invocation. |
37| [`function-fan-out.ts.template`](assets/templates/function-fan-out.ts.template) | `src/inngest/functions/{name}.ts` | Orchestrator that receives one event and fans out work — via `step.sendEvent` (fire-and-forget) or `Promise.all(step.invoke(...))` (wait for results). |
38
39## Quick Reference
40
41- **Event name shape:** `domain/entity.verb_past` — e.g., `app/user.created`, `shop/order.placed`, `billing/invoice.paid`. The conventions doc has the full rule.
42- **Function `id`:** stable kebab-case, **never rename it** after deploy — Inngest uses it as the durable state key.
43- **Step `id`:** descriptive kebab-case, also durable; changing one re-runs that step on replay.
44- **Idempotency:** every `step.run` must be safely retryable. If it calls a third-party API that mutates state, pass an idempotency key.
45- **Dev server:** `npx inngest-cli@latest dev`, then UI at http://localhost:8288. Set `INNGEST_DEV=1` in `.env.local` to point the SDK at it.
46
47## Project Layout Assumption
48
49Templates assume a `src/` directory with App Router:
50
51```
52src/
53├── app/api/inngest/route.ts # generated by route-handler template
54└── inngest/
55 ├── client.ts # generated by client template
56 ├── events/ # one file per domain (events.shop.ts, events.user.ts)
57 │ └── {domain}.ts # generated by event template
58 └── functions/
59 ├── index.ts # registry: re-exports all functions as an array
60 └── {name}.ts # generated by function/cron/fan-out templates
61```
62
63If your project uses a different layout (no `src/`, or Pages Router), override the output paths in `config.json`. The conventions doc explains the trade-offs.
64
65## Setup
66
67`config.json` controls path overrides. On first use, check whether its fields are filled in — if not, ask the user before generating (project root differs from the default assumption).
68
69## Related Skills
70
71- `vercel:ai-sdk` — if your Inngest functions invoke LLMs, the AI SDK gives you streaming + provider abstraction; pair them with `step.run` for retry safety.
72- `vercel:vercel-functions` — Inngest's `serve()` adapter for Next.js runs as a Vercel Serverless Function by default; the AI SDK skill has notes on Fluid Compute and timeout extension for long agent runs.