首页
/ Next.js 集成 Inngest:基于官方示例构建持久化工作流的完整实践指南

Next.js 集成 Inngest:基于官方示例构建持久化工作流的完整实践指南

2026-09-06 17:23:32作者:羿妍玫Ivan

本篇指南基于 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],
};

逐段解读:

  1. 事件类型 Events:以“事件名 → 事件结构”的 Record 形式声明类型。这里只定义了 test/hello.world 一个事件,其 data 携带一个 message: string 字段。类型声明与 Inngest 客户端绑定后,inngest.send(...) 与函数触发条件都会获得编译期校验。
  2. 客户端 new Inngest({...})id: "demo-app" 作为客户端标识;schemas: new EventSchemas().fromRecord<Events>() 把上面的类型注册为客户端 Schema,使发送/接收两侧共用同一套类型约束。
  3. 函数 helloWorld:通过 inngest.createFunction 声明,第二个参数 { event: "test/hello.world" } 指定触发器(订阅该事件即执行);函数体内用 step.sleep("sleep for a second", "1s") 演示了一个 step 操作——step 是 Inngest 实现“durable(持久化)”执行的基本单元,执行状态会被记录,可安全重试与恢复。源码注释也提示:实际项目中每个函数“typically in its own file(通常放在独立文件里)”,functions 数组再统一收集。
  4. 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/nextserve 函数:它返回 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.xinngest-clilatest,另有 concurrently ^8.2.1 负责双进程编排、typescript 5.5.4 与 React 18.2.0,均可在 package.json 中确认。

部署到云端的前置条件

README 明确指出:完整的云端部署需要两项前置配置:

  1. 一个 Inngest Cloud 账号;
  2. 在托管平台(如 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 --turboinngest-cli dev

基于此骨架,后续扩展的方向是:在 src/inngest/ 下按“每函数一文件”的方式新增工作流、在 functions 数组中注册、并通过 Events 类型为新事件补充 Schema——类型系统会把发送端与消费端一起校验。

登录后查看全文
热门项目推荐
相关项目推荐