---
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
  - /docs/deploying/world/vercel-world
---

<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]
```

<Accordion type="single" collapsible>
  <AccordionItem value="typescript-intellisense" className="[&_h3]:my-0">
    <AccordionTrigger className="text-sm">
      ### Setup IntelliSense for TypeScript (Optional)
    </AccordionTrigger>
    <AccordionContent className="[&_p]:my-2">

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]
      }
    ]
  }
}
```

    </AccordionContent>
  </AccordionItem>
</Accordion>

<Accordion type="single" collapsible>
  <AccordionItem value="configure-proxy-handler" className="[&_h3]:my-0">
    <AccordionTrigger className="text-sm">
      <h3 id="configure-proxy-handler">Configure Proxy Handler (if applicable)</h3>
    </AccordionTrigger>
    <AccordionContent className="[&_p]:my-2">

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 is especially easy to miss 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.
    </AccordionContent>
  </AccordionItem>
</Accordion>

</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, but let's take a look at this code:

* 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

Let's now define those 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 step is invoked inside a **workflow**, it gets enqueued to run on a separate request while the workflow is suspended, just like `sleep`.
* 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:

```
Build error occurred
Error: Cannot find module 'next/dist/lib/server-external-packages.json'
```

Upgrade to `workflow@4.0.1-beta.26` or later:

```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:

```
'start' received an invalid workflow function. Ensure the Workflow Development Kit 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).
