---
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 development kit 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
---

# Next.js





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

    <CodeBlockTabs defaultValue="npm">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="npm">
          npm
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="pnpm">
          pnpm
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="yarn">
          yarn
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="bun">
          bun
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab value="npm">
        ```bash
        npm i workflow
        ```
      </CodeBlockTab>

      <CodeBlockTab value="pnpm">
        ```bash
        pnpm add workflow
        ```
      </CodeBlockTab>

      <CodeBlockTab value="yarn">
        ```bash
        yarn add workflow
        ```
      </CodeBlockTab>

      <CodeBlockTab value="bun">
        ```bash
        bun add workflow
        ```
      </CodeBlockTab>
    </CodeBlockTabs>

    ### 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="typescript-intellisense" className="[&_h3]:my-0">
        <AccordionTrigger className="text-sm">
          ### Configure Proxy Handler (if applicable)
        </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.

          Add `.well-known/workflow/*` to your middleware's 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
```

<img alt="Workflow SDK Web UI" src={__img0} placeholder="blur" />

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

<CodeBlockTabs defaultValue="npm">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="npm">
      npm
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="pnpm">
      pnpm
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="yarn">
      yarn
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="bun">
      bun
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="npm">
    ```bash
    npm install workflow@latest
    ```
  </CodeBlockTab>

  <CodeBlockTab value="pnpm">
    ```bash
    pnpm add workflow@latest
    ```
  </CodeBlockTab>

  <CodeBlockTab value="yarn">
    ```bash
    yarn add workflow@latest
    ```
  </CodeBlockTab>

  <CodeBlockTab value="bun">
    ```bash
    bun add workflow@latest
    ```
  </CodeBlockTab>
</CodeBlockTabs>

## Next Steps

* Learn more about the [Foundations](/docs/foundations).
* Check [Errors](/docs/errors) if you encounter issues.
* Explore the [API Reference](/docs/api-reference).


## Sitemap
[Overview of all docs pages](/sitemap.md)
