UNPKG

workflow

Version:

Workflow SDK - Build durable, resilient, and observable workflows

331 lines (226 loc) • 10.5 kB
--- title: Next.js description: This guide will walk through setting up your first workflow in a Next.js app. Along the way, you'll learn more about the concepts that are fundamental to using the Workflow SDK in your own projects. type: guide summary: Set up Workflow SDK in a Next.js app. prerequisites: - /docs/getting-started related: - /docs/api-reference/workflow-next - /worlds/vercel --- <CopyPrompt text="In this Next.js app, run `npm i workflow`. Wrap `next.config.ts` with `withWorkflow` from `workflow/next`. If the app has `proxy.ts` or middleware, exclude `.well-known/workflow/` from its matcher. Add `workflows/user-signup.ts` exporting `handleUserSignup(email)` with `&quot;use workflow&quot;`, `sleep` from `workflow`, and `&quot;use step&quot;` helper functions that create a user, send a welcome email, and send an onboarding email. Add `app/api/signup/route.ts` with a POST handler that reads `{ email }`, calls `start(handleUserSignup, [email])` from `workflow/api`, and returns JSON. Run `npm run dev`, trigger `curl -X POST --json '{&quot;email&quot;:&quot;hello@example.com&quot;}' http://localhost:3000/api/signup`, and inspect with `npx workflow web` or `npx workflow inspect runs`." /> <Steps> <Step> ## Create your Next.js project Start by creating a new Next.js project. This command will create a new directory named `my-workflow-app` and set up a Next.js project inside it. ```bash npm create next-app@latest my-workflow-app ``` Enter the newly created directory: ```bash cd my-workflow-app ``` ### Install `workflow` ```package-install npm i workflow ``` ### Configure Next.js Wrap your `next.config.ts` with `withWorkflow()`. This enables usage of the `"use workflow"` and `"use step"` directives. ```typescript title="next.config.ts" lineNumbers import { withWorkflow } from "workflow/next"; // [!code highlight] import type { NextConfig } from "next"; const nextConfig: NextConfig = { // … rest of your Next.js config }; export default withWorkflow(nextConfig); // [!code highlight] ``` <Details> <Summary className="[&_h3]:my-0"> ### Setup IntelliSense for TypeScript (Optional) </Summary> To enable helpful hints in your IDE, setup the workflow plugin in `tsconfig.json`: ```json title="tsconfig.json" lineNumbers { "compilerOptions": { // ... rest of your TypeScript config "plugins": [ { "name": "workflow" // [!code highlight] } ] } } ``` </Details> <Details> <Summary className="[&_h3]:my-0"> <h3 id="configure-proxy-handler" className="scroll-m-28">Configure Proxy Handler (if applicable)</h3> </Summary> If your Next.js app has a [proxy handler](https://nextjs.org/docs/app/api-reference/file-conventions/proxy) (formerly known as "middleware"), you'll need to update the matcher pattern to exclude Workflow's internal paths to prevent the proxy handler from running on them. If you see `[local world] Queue operation failed` with `Cannot perform ArrayBuffer.prototype.slice on a detached ArrayBuffer`, your proxy matcher is still intercepting Workflow's internal `POST /.well-known/workflow/v1/flow` request. This issue can be hard to spot in Next.js 16, where `proxy.ts` replaced `middleware.ts`. Add `.well-known/workflow/*` to your matcher exclusion list: ```typescript title="proxy.ts" lineNumbers import { NextResponse } from "next/server"; import type { NextRequest } from "next/server"; export function proxy(request: NextRequest) { // Your middleware logic return NextResponse.next(); } export const config = { matcher: [ // ... your existing matchers { source: "/((?!_next/static|_next/image|favicon.ico|.well-known/workflow/).*)", // [!code highlight] }, ], }; ``` This ensures that internal Workflow paths are not intercepted by your middleware, which could interfere with workflow execution and resumption. </Details> </Step> <Step> ## Create your first workflow Create a new file for our first workflow: ```typescript title="workflows/user-signup.ts" lineNumbers import { sleep } from "workflow"; export async function handleUserSignup(email: string) { "use workflow"; // [!code highlight] const user = await createUser(email); await sendWelcomeEmail(user); await sleep("5s"); // Pause for 5s - doesn't consume any resources await sendOnboardingEmail(user); console.log("Workflow is complete! Run 'npx workflow web' to inspect your run") return { userId: user.id, status: "onboarded" }; } ``` We'll fill in those functions next. The current code does the following: * We define a **workflow** function with the directive `"use workflow"`. Think of the workflow function as the _orchestrator_ of individual **steps**. * The Workflow SDK's `sleep` function allows us to suspend execution of the workflow without using up any resources. A sleep can be a few seconds, hours, days, or even months long. ## Create your workflow steps Define the missing functions. ```typescript title="workflows/user-signup.ts" lineNumbers import { FatalError } from "workflow" // Our workflow function defined earlier async function createUser(email: string) { "use step"; // [!code highlight] console.log(`Creating user with email: ${email}`); // Full Node.js access - database calls, APIs, etc. return { id: crypto.randomUUID(), email }; } async function sendWelcomeEmail(user: { id: string; email: string; }) { "use step"; // [!code highlight] console.log(`Sending welcome email to user: ${user.id}`); if (Math.random() < 0.3) { // By default, steps will be retried for unhandled errors throw new Error("Retryable!"); } } async function sendOnboardingEmail(user: { id: string; email: string}) { "use step"; // [!code highlight] if (!user.email.includes("@")) { // To skip retrying, throw a FatalError instead throw new FatalError("Invalid Email"); } console.log(`Sending onboarding email to user: ${user.id}`); } ``` Taking a look at this code: * Business logic lives inside **steps**. When a workflow invokes a step, the workflow suspends while the step runs with full Node.js access. The combined handler may execute it inline; queued retries and continuations return through the same flow route. * If a step throws an error, like in `sendWelcomeEmail`, the step will automatically be retried until it succeeds (or hits the step's max retry count). * Steps can throw a `FatalError` if an error is intentional and should not be retried. <Callout> We'll dive deeper into workflows, steps, and other ways to suspend or handle events in [Foundations](/docs/foundations). </Callout> </Step> <Step> ## Create your route handler To invoke your new workflow, we'll need to add your workflow to a `POST` API Route Handler, `app/api/signup/route.ts`, with the following code: ```typescript title="app/api/signup/route.ts" import { start } from "workflow/api"; import { handleUserSignup } from "@/workflows/user-signup"; import { NextResponse } from "next/server"; export async function POST(request: Request) { const { email } = await request.json(); // Executes asynchronously and doesn't block your app await start(handleUserSignup, [email]); return NextResponse.json({ message: "User signup workflow started", }); } ``` This Route Handler creates a `POST` request endpoint at `/api/signup` that will trigger your workflow. <Callout> Workflows can be triggered from API routes, Server Actions, or any server-side code. </Callout> </Step> </Steps> ## Run in development To start your development server, run the following command in your terminal in the Next.js root directory: ```bash npm run dev ``` Once your development server is running, you can trigger your workflow by running this command in the terminal: ```bash curl -X POST --json '{"email":"hello@example.com"}' http://localhost:3000/api/signup ``` Check the Next.js development server logs to see your workflow execute, as well as the steps that are being processed. Additionally, you can use the [Workflow SDK CLI or Web UI](/docs/observability) to inspect your workflow runs and steps in detail. ```bash # Open the observability Web UI npx workflow web # or if you prefer a terminal interface, use the CLI inspect command npx workflow inspect runs ``` ![Workflow SDK Web UI](/o11y-ui.png) ## Deploying to production Workflow SDK apps currently work best when deployed to [Vercel](https://vercel.com/home) and need no special configuration. <FluidComputeCallout /> Check the [Deploying](/docs/deploying) section to learn how your workflows can be deployed elsewhere. ## Troubleshooting ### Next.js 16.1+ compatibility If you see this error when upgrading to Next.js 16.1 or later: ```text Build error occurred Error: Cannot find module 'next/dist/lib/server-external-packages.json' ``` Upgrade `workflow` to the latest release: ```package-install workflow@latest ``` ### Turborepo caching If you're using [Turborepo](https://turbo.build/repo) in a monorepo, you need to include the generated Workflow routes in your cache outputs. The Workflow SDK generates route handlers at `app/.well-known/workflow/` (or `src/app/.well-known/workflow/` if your project uses the `src` directory) during the build process, and these files must be cached alongside your Next.js build output. Add the following to your `turbo.json`: ```jsonc title="turbo.json" { "tasks": { "build": { "outputs": [ ".next/**", "!.next/cache/**", // Include whichever path matches your project layout "app/.well-known/workflow/**", "src/app/.well-known/workflow/**" ] } } } ``` Without this configuration, you may experience intermittent issues where workflows fail to register properly on cache hits, while working correctly on cache misses. ### `start()` says it received an invalid workflow function If you see this error: ```text 'start' received an invalid workflow function. Ensure the Workflow SDK is configured correctly and the function includes a 'use workflow' directive. ``` Check both of these first: 1. The workflow function includes `"use workflow"`. 2. Your `next.config.ts` is wrapped with [`withWorkflow()`](/docs/api-reference/workflow-next/with-workflow). See [start-invalid-workflow-function](/docs/errors/start-invalid-workflow-function) for full examples and fixes. ## Next steps * Learn more about the [Foundations](/docs/foundations). * Check [Errors](/docs/errors) if you encounter issues. * Explore the [API Reference](/docs/api-reference).