Next.js Setup Guide
Works with App Router, Pages Router, and Server Actions.
Prerequisites
- Next.js project with TypeScript
- Trigger.dev account + project created
Setup
1. Run CLI Init
bash
npx trigger.dev@latest initThis will:
- Ask if you want to install the Trigger.dev MCP server
- Log you in if not already authenticated
- Ask you to select your Trigger.dev project
- Install SDK packages
- Create a
/triggerdirectory with an example task - Create
trigger.config.tsin your project root
Install the "Hello World" example task when prompted.
2. Start the Dev Server
bash
npx trigger.dev@latest devRun this alongside your Next.js dev server. The CLI watches your /trigger directory and communicates with Trigger.dev.
Tip: Run both concurrently:
json
{
"scripts": {
"trigger:dev": "npx trigger.dev@latest dev",
"dev": "npx concurrently --kill-others --names \"next,trigger\" \"next dev\" \"npm run trigger:dev\""
}
}3. Test in Dashboard
Visit the Test page in your Trigger.dev dashboard → select the Example task → click "Run test".
4. Set Secret Key
Add to .env.local (App Router) or .env (Pages Router):
bash
TRIGGER_SECRET_KEY=your-dev-secret-keyGet your key from the dashboard → API Keys → DEV secret key.
Triggering Tasks
App Router — Route Handler
ts
// app/api/hello-world/route.ts
import type { helloWorldTask } from "@/trigger/example";
import { tasks } from "@trigger.dev/sdk";
import { NextResponse } from "next/server";
export async function GET() {
const handle = await tasks.trigger<typeof helloWorldTask>(
"hello-world",
"James"
);
return NextResponse.json(handle);
}App Router — Server Actions
ts
// app/api/actions.ts
"use server";
import type { helloWorldTask } from "@/trigger/example";
import { tasks } from "@trigger.dev/sdk";
export async function triggerHelloWorld() {
const handle = await tasks.trigger<typeof helloWorldTask>(
"hello-world",
"James"
);
return { handle };
}tsx
// app/page.tsx
"use client";
import { triggerHelloWorld } from "./actions";
export default function Home() {
return (
<button onClick={async () => await triggerHelloWorld()}>
Trigger task
</button>
);
}Pages Router — API Route
ts
// pages/api/hello-world.ts
import { tasks } from "@trigger.dev/sdk";
import type { NextApiRequest, NextApiResponse } from "next";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const handle = await tasks.trigger("hello-world", "James");
res.status(200).json(handle);
}Sync Vercel Environment Variables (Optional)
ts
// trigger.config.ts
import { defineConfig } from "@trigger.dev/sdk";
import { syncVercelEnvVars } from "@trigger.dev/build/extensions/core";
export default defineConfig({
project: "<project-ref>",
build: {
extensions: [syncVercelEnvVars()],
},
});Requires VERCEL_ACCESS_TOKEN and VERCEL_PROJECT_ID env vars.
Deploy
bash
npx trigger.dev@latest deployTroubleshooting
Next.js Build Fails in CI (Missing API Key)
Mark affected pages as dynamic:
ts
export const dynamic = "force-dynamic";Revalidation from Tasks
To trigger ISR revalidation from a Trigger.dev task:
ts
// app/api/revalidate/path/route.ts
import { NextRequest, NextResponse } from "next/server";
import { revalidatePath } from "next/cache";
export async function POST(request: NextRequest) {
const { path, secret } = await request.json();
if (secret !== process.env.REVALIDATION_SECRET) {
return NextResponse.json({ message: "Invalid secret" }, { status: 401 });
}
revalidatePath(path);
return NextResponse.json({ revalidated: true });
}ts
// trigger/revalidate-path.ts
import { logger, task } from "@trigger.dev/sdk";
export const revalidatePath = task({
id: "revalidate-path",
run: async (payload: { path: string }) => {
const response = await fetch(`${process.env.NEXTJS_APP_URL}/api/revalidate/path`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
path: payload.path,
secret: process.env.REVALIDATION_SECRET,
}),
});
logger.log("Revalidation result", { ok: response.ok });
return { success: response.ok };
},
});React Event Handlers
tsx
// Works:
<Button onClick={() => myTask.trigger(data)}>Trigger</Button>
// Doesn't work (method not bound):
<Button onClick={myTask.trigger}>Trigger</Button>