Next.js 集成 Inngest:基于官方示例构建持久化工作流的完整实践指南
本篇指南基于 Next.js 官方仓库中的 examples/inngest 示例,讲解如何在 Next.js(App Router)应用中接入 Inngest,通过 Server Action 发送事件、用 API Route 挂载 Inngest 服务、并借助本地 dev server 获得即时的执行反馈。读完后你将掌握该示例的完整目录结构、事件 Schema 与函数定义的 TypeScript 写法、本地双进程(Next.js + Inngest dev server)的启动方式,以及部署到云端时需要的前置条件。
示例定位:极简但完整的持久化工作流骨架
该示例的设计目标是刻意保持“bare bones(极简)”:
- 只有一个按钮的 UI,点击即触发一次 Inngest 事件;
- 本地运行 Inngest dev server,让开发者无需配置任何云账号就能看到函数的执行过程。
也就是说,它演示了完整的“发送事件 → 路由分发 → 函数执行”链路,同时把所有与业务无关的部分裁剪到最少,适合作为集成 Inngest 的起点模板。
使用 create-next-app 脚手架引导项目
按照 README 的说明,可以用任意主流包管理器执行 create-next-app 并指定 --example inngest 参数来引导本项目:
# npm
npx create-next-app --example inngest inngest-app
# Yarn
yarn create next-app --example inngest inngest-app
# pnpm
pnpm create next-app --example inngest inngest-app
# Bun
bunx create-next-app --example inngest inngest-app
执行后得到的就是一个由 create-next-app 引导的标准 Next.js 项目(即本仓库中的 examples/inngest 目录内容)。
从 create-next-app 的源码可以看到其示例分发机制:当 --example 参数是仓库内置示例名(而非 URL)时,examples.ts 中的 downloadAndExtractExample 会下载 Next.js 仓库 canary 分支的 tar 包,并用 p.includes('next.js-canary/examples/${name}/') 过滤出对应示例目录解压到本地;existsInRepo 则通过校验官方仓库 examples/<name>/package.json 是否可访问来判断该示例是否存在。因此示例中的依赖版本(如 next: "latest")在脚手架化后始终对齐最新的发布线。
示例项目目录结构与关键文件
引导完成后,项目结构如下(与仓库中 examples/inngest 目录一致):
inngest-app/
├── src/
│ ├── app/
│ │ ├── api/inngest/
│ │ │ └── route.ts # Inngest API 路由(serve 挂载点)
│ │ ├── layout.tsx # 根布局(示例中仅设置 metadata)
│ │ └── page.tsx # 唯一页面:按钮触发事件
│ └── inngest/
│ └── inngest.config.ts # 事件 Schema、客户端与函数定义
├── next.config.js
├── package.json
└── tsconfig.json
其中 next.config.js 是空的 const nextConfig = {};,说明该示例不需要任何额外的 Next.js 配置项;而 tsconfig.json 通过 "paths": { "@/*": ["./src/*"] } 配置了 @ 别名,这也是下面源码中 @/inngest/inngest.config 导入路径能生效的原因。
事件 Schema 与 Inngest 客户端定义
核心配置集中在 src/inngest/inngest.config.ts,它由四部分组成:
import { EventSchemas, Inngest } from "inngest";
// TypeScript schema for the events
export type Events = {
"test/hello.world": {
name: "test/hello.world";
data: {
message: string;
};
};
};
// Inngest client to send and receive events
export const inngest = new Inngest({
id: "demo-app",
schemas: new EventSchemas().fromRecord<Events>(),
});
// a function to execute, typically in its own file
const helloWorld = inngest.createFunction(
{ id: "hello-world", name: "Hello World" },
{ event: "test/hello.world" },
async ({ event, step }) => {
await step.sleep("sleep for a second", "1s");
return { event, body: event.data.message };
},
);
// configuration for the Inngest api router
export const inngestConfig = {
client: inngest,
functions: [helloWorld],
};
逐段解读:
- 事件类型
Events:以“事件名 → 事件结构”的 Record 形式声明类型。这里只定义了test/hello.world一个事件,其data携带一个message: string字段。类型声明与 Inngest 客户端绑定后,inngest.send(...)与函数触发条件都会获得编译期校验。 - 客户端
new Inngest({...}):id: "demo-app"作为客户端标识;schemas: new EventSchemas().fromRecord<Events>()把上面的类型注册为客户端 Schema,使发送/接收两侧共用同一套类型约束。 - 函数
helloWorld:通过inngest.createFunction声明,第二个参数{ event: "test/hello.world" }指定触发器(订阅该事件即执行);函数体内用step.sleep("sleep for a second", "1s")演示了一个 step 操作——step 是 Inngest 实现“durable(持久化)”执行的基本单元,执行状态会被记录,可安全重试与恢复。源码注释也提示:实际项目中每个函数“typically in its own file(通常放在独立文件里)”,functions数组再统一收集。 inngestConfig:聚合“客户端 + 函数列表”,作为 API 路由的挂载配置导出。
通过 API Route 挂载 Inngest 服务
src/app/api/inngest/route.ts 只有三行:
import { inngestConfig } from "@/inngest/inngest.config";
import { serve } from "inngest/next";
export const { GET, POST, PUT } = serve(inngestConfig);
这里使用了 inngest 官方 SDK 的 Next.js 适配入口 inngest/next 的 serve 函数:它返回 GET(服务发现/函数列表)、POST(执行请求)、PUT(服务端同步)三个 HTTP 处理函数,直接以 Next.js App Router 的 route handler 约定导出即可。于是 Inngest 平台与 dev server 都会把 http://localhost:3000/api/inngest 当作该应用的执行端点。
用一个按钮触发事件:Server Action 发事件
src/app/page.tsx 是整个示例的“UI 层”:
import { inngest } from "@/inngest/inngest.config";
import { redirect } from "next/navigation";
export default function Home() {
async function triggerInngestEvent() {
"use server";
await inngest.send({
name: "test/hello.world",
data: {
message: "Hello from Next.js!",
},
});
redirect("http://localhost:8288/stream");
}
return (
<main>
<div>
<form action={triggerInngestEvent}>
<button type="submit">Trigger Your Inngest Function</button>
</form>
</div>
</main>
);
}
几个值得注意的实现细节:
"use server"指令:triggerInngestEvent是一个 Server Action,事件发送发生在服务端,inngest客户端(及其配置)不会被打进浏览器 bundle,也不会暴露任何密钥。inngest.send(...):发出test/hello.world事件,data.message为"Hello from Next.js!",与Events类型严格对应。redirect(...):发送成功后重定向到http://localhost:8288/stream——这正是本地 Inngest dev server 的执行流页面,用户能立刻看到hello-world函数的执行过程(包括那次 1 秒的step.sleep)。这就是 README 中“immediate feedback(即时反馈)”说法的具体落地。
本地开发:Next.js 与 Inngest dev server 双进程并跑
示例 package.json 的 scripts 定义了完整的本地开发链路:
{
"scripts": {
"dev": "concurrently \"npm:dev:*\"",
"dev:next": "next dev --turbo",
"dev:inngest": "inngest-cli dev --no-discovery -u http://localhost:3000/api/inngest",
"build": "next build",
"start": "next start"
}
}
npm run dev(或yarn dev/pnpm dev/bun dev)通过concurrently并行启动两个子进程:dev:next:启动 Next.js 开发服务器,--turbo即启用 Turbopack 加速开发构建;dev:inngest:启动inngest-cli dev,--no-discovery关闭服务发现,-u http://localhost:3000/api/inngest显式指向上面挂载的 API Route。
启动后:
- 应用页面在 http://localhost:3000 打开;
- Inngest dev server 运行在 http://localhost:8288,README 特别提示“它可能需要几秒才能启动完成”——点击按钮后的重定向目标
http://localhost:8288/stream就落在该服务上。
依赖方面,inngest 锁定为 3.x,inngest-cli 为 latest,另有 concurrently ^8.2.1 负责双进程编排、typescript 5.5.4 与 React 18.2.0,均可在 package.json 中确认。
部署到云端的前置条件
README 明确指出:完整的云端部署需要两项前置配置:
- 一个 Inngest Cloud 账号;
- 在托管平台(如 Vercel)配置 Inngest 集成,让平台自动处理生产环境下的事件路由与服务发现。
本地流程(dev server + --no-discovery)与生产流程(Cloud 平台 + 集成发现)的差别正体现在 dev:inngest 脚本的 --no-discovery 参数上:本地是手动指定 URL,云端则依赖平台集成自动发现。
小结
examples/inngest 用不超过五个文件演示了 Next.js 接入 Inngest 的最小完整闭环:
| 环节 | 文件 | 职责 |
|---|---|---|
| 事件 Schema / 客户端 / 函数 | src/inngest/inngest.config.ts | 类型化事件定义、createFunction 与 step |
| 服务挂载 | src/app/api/inngest/route.ts | serve(inngestConfig) 导出 GET/POST/PUT |
| 触发入口 | src/app/page.tsx | Server Action 发送事件并重定向到 dev server |
| 进程编排 | package.json | concurrently 并跑 next dev --turbo 与 inngest-cli dev |
基于此骨架,后续扩展的方向是:在 src/inngest/ 下按“每函数一文件”的方式新增工作流、在 functions 数组中注册、并通过 Events 类型为新事件补充 Schema——类型系统会把发送端与消费端一起校验。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00