Build Production Background Jobs with Trigger.dev v3 and Next.js

Modern web applications need to handle work that should not block a user request — sending emails, processing images, syncing data with third-party APIs, generating reports, or running AI pipelines. Without a background job system, you are stuck with fragile workarounds: setTimeout hacks, edge function timeouts, or spinning up a separate worker server.
Trigger.dev v3 solves this by giving you a TypeScript-native background job framework that runs your tasks on managed infrastructure with built-in retries, logging, and observability. You write tasks as plain TypeScript functions, trigger them from your Next.js API routes or Server Actions, and Trigger.dev handles the rest — execution, retries, scheduling, and monitoring.
In this tutorial, you will build a user onboarding automation system — when a new user signs up, a background workflow sends a welcome email, generates a personalized avatar, syncs the user to a CRM, and schedules a follow-up email for 3 days later. Along the way, you will master Trigger.dev v3 tasks, error handling, multi-step workflows, scheduled jobs, and production deployment.
Prerequisites
Before starting, make sure you have:
- Node.js 20+ installed
- A free Trigger.dev account — sign up at the Trigger.dev website
- Basic knowledge of React and TypeScript
- Familiarity with Next.js App Router (API routes, Server Actions)
- A code editor (VS Code recommended)
What You Will Build
A user onboarding automation system with:
- Welcome email task — sends a formatted email when a user signs up
- Avatar generation task — creates a personalized avatar using an external API
- CRM sync task — pushes new user data to an external CRM
- Multi-step onboarding workflow — orchestrates all tasks in sequence with error handling
- Scheduled follow-up — sends a follow-up email 3 days after signup
- Cron job — daily digest of new signups for the admin team
- Full observability — logs, runs, and errors visible in the Trigger.dev dashboard
Step 1: Create the Next.js Project
Scaffold a new Next.js 15 project:
npx create-next-app@latest trigger-onboarding --typescript --tailwind --eslint --app --src-dir --use-npm
cd trigger-onboardingInstall the Trigger.dev SDK and CLI:
npm install @trigger.dev/sdk
npm install -D trigger.devThe @trigger.dev/sdk package provides the runtime API for defining and triggering tasks. The trigger.dev CLI handles local development and deployment.
Step 2: Initialize Trigger.dev
Run the init command to set up Trigger.dev in your project:
npx trigger.dev@latest initThis command does several things:
- Creates a
trigger.config.tsfile at the project root - Creates a
src/trigger/directory for your task files - Adds your project reference and API key to
.env.local - Updates
package.jsonwith dev and deploy scripts
Your trigger.config.ts should look like this:
import { defineConfig } from "@trigger.dev/sdk/v3";
export default defineConfig({
project: "proj_your_project_id",
runtime: "node",
logLevel: "log",
retries: {
enabledInDev: true,
default: {
maxAttempts: 3,
minTimeoutInMs: 1000,
maxTimeoutInMs: 10000,
factor: 2,
},
},
dirs: ["src/trigger"],
});The retries configuration is important — it sets a default retry policy for all tasks. When a task fails, Trigger.dev will retry it up to 3 times with exponential backoff (1s, 2s, 4s delays).
Step 3: Create Your First Task — Welcome Email
Create a file at src/trigger/welcome-email.ts:
import { task, logger } from "@trigger.dev/sdk/v3";
export interface WelcomeEmailPayload {
userId: string;
email: string;
name: string;
}
export const sendWelcomeEmail = task({
id: "send-welcome-email",
retry: {
maxAttempts: 5,
},
run: async (payload: WelcomeEmailPayload) => {
logger.info("Sending welcome email", {
userId: payload.userId,
email: payload.email,
});
// In production, replace with your email provider (Resend, SendGrid, etc.)
const response = await fetch("https://api.resend.com/emails", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.RESEND_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
from: "welcome@yourapp.com",
to: payload.email,
subject: `Welcome to Our App, ${payload.name}!`,
html: `
<h1>Welcome, ${payload.name}!</h1>
<p>We are excited to have you on board.</p>
<p>Here are some things you can do to get started:</p>
<ul>
<li>Complete your profile</li>
<li>Explore the dashboard</li>
<li>Connect your first integration</li>
</ul>
`,
}),
});
if (!response.ok) {
const error = await response.text();
throw new Error(`Failed to send email: ${error}`);
}
const result = await response.json();
logger.info("Welcome email sent successfully", {
emailId: result.id,
});
return { emailId: result.id, sentAt: new Date().toISOString() };
},
});Key things to notice:
task()defines a background task with a uniqueidloggerprovides structured logging visible in the Trigger.dev dashboardretryoverrides the default retry policy — emails are critical, so we retry 5 times- Throwing an error triggers a retry automatically
- The return value is stored and visible in the dashboard for debugging
Step 4: Create the Avatar Generation Task
Create src/trigger/generate-avatar.ts:
import { task, logger } from "@trigger.dev/sdk/v3";
export interface AvatarPayload {
userId: string;
name: string;
}
export const generateAvatar = task({
id: "generate-avatar",
retry: {
maxAttempts: 3,
},
run: async (payload: AvatarPayload) => {
logger.info("Generating avatar", { userId: payload.userId });
// Use DiceBear API for avatar generation
const initials = payload.name
.split(" ")
.map((n) => n[0])
.join("")
.toUpperCase();
const seed = encodeURIComponent(payload.name);
const avatarUrl = `https://api.dicebear.com/8.x/initials/svg?seed=${seed}&chars=${initials.length}`;
// Download the SVG and convert to a stored URL
const response = await fetch(avatarUrl);
if (!response.ok) {
throw new Error(`Avatar generation failed: ${response.statusText}`);
}
const svgContent = await response.text();
// In production, upload to your storage (S3, Cloudflare R2, etc.)
// For this tutorial, we simulate storage
const storedUrl = `/avatars/${payload.userId}.svg`;
logger.info("Avatar generated", {
userId: payload.userId,
url: storedUrl,
});
return { avatarUrl: storedUrl, svgSize: svgContent.length };
},
});Step 5: Create the CRM Sync Task
Create src/trigger/sync-crm.ts:
import { task, logger } from "@trigger.dev/sdk/v3";
export interface CrmSyncPayload {
userId: string;
email: string;
name: string;
signupDate: string;
}
export const syncToCrm = task({
id: "sync-to-crm",
retry: {
maxAttempts: 4,
minTimeoutInMs: 2000,
maxTimeoutInMs: 30000,
factor: 3,
},
run: async (payload: CrmSyncPayload) => {
logger.info("Syncing user to CRM", {
userId: payload.userId,
email: payload.email,
});
// Simulate CRM API call (replace with HubSpot, Salesforce, etc.)
const crmResponse = await fetch(
`${process.env.CRM_API_URL}/contacts`,
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.CRM_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
email: payload.email,
name: payload.name,
properties: {
signup_date: payload.signupDate,
source: "web_app",
lifecycle_stage: "subscriber",
},
}),
}
);
if (!crmResponse.ok) {
const error = await crmResponse.text();
logger.error("CRM sync failed", { error });
throw new Error(`CRM sync failed: ${error}`);
}
const contact = await crmResponse.json();
logger.info("CRM sync completed", {
crmContactId: contact.id,
});
return { crmContactId: contact.id };
},
});Notice the retry configuration here — CRM APIs can be flaky, so we use a more aggressive backoff (factor: 3) with a higher maximum timeout of 30 seconds.
Step 6: Build the Multi-Step Onboarding Workflow
Now for the powerful part — orchestrating all tasks into a single workflow. Create src/trigger/onboarding-workflow.ts:
import { task, logger, wait } from "@trigger.dev/sdk/v3";
import { sendWelcomeEmail } from "./welcome-email";
import { generateAvatar } from "./generate-avatar";
import { syncToCrm } from "./sync-crm";
export interface OnboardingPayload {
userId: string;
email: string;
name: string;
}
export const onboardingWorkflow = task({
id: "onboarding-workflow",
retry: {
maxAttempts: 1, // The workflow itself should not retry — individual tasks handle retries
},
run: async (payload: OnboardingPayload) => {
logger.info("Starting onboarding workflow", {
userId: payload.userId,
});
// Step 1: Send welcome email and generate avatar in parallel
const [emailResult, avatarResult] = await Promise.all([
sendWelcomeEmail.triggerAndWait({
userId: payload.userId,
email: payload.email,
name: payload.name,
}),
generateAvatar.triggerAndWait({
userId: payload.userId,
name: payload.name,
}),
]);
logger.info("Email and avatar completed", {
emailOk: emailResult.ok,
avatarOk: avatarResult.ok,
});
// Step 2: Sync to CRM (depends on previous steps completing)
const crmResult = await syncToCrm.triggerAndWait({
userId: payload.userId,
email: payload.email,
name: payload.name,
signupDate: new Date().toISOString(),
});
logger.info("CRM sync completed", { crmOk: crmResult.ok });
// Step 3: Schedule a follow-up email for 3 days later
const followUpHandle = await sendFollowUpEmail.trigger(
{
userId: payload.userId,
email: payload.email,
name: payload.name,
},
{
delay: "3d", // Trigger.dev supports human-readable delays
}
);
logger.info("Follow-up email scheduled", {
followUpId: followUpHandle.id,
});
return {
emailResult: emailResult.ok ? emailResult.output : null,
avatarResult: avatarResult.ok ? avatarResult.output : null,
crmResult: crmResult.ok ? crmResult.output : null,
followUpScheduled: followUpHandle.id,
};
},
});
// Follow-up email task
const sendFollowUpEmail = task({
id: "send-follow-up-email",
retry: {
maxAttempts: 5,
},
run: async (payload: OnboardingPayload) => {
logger.info("Sending follow-up email", {
userId: payload.userId,
});
const response = await fetch("https://api.resend.com/emails", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.RESEND_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
from: "hello@yourapp.com",
to: payload.email,
subject: `How's it going, ${payload.name}?`,
html: `
<h1>Hey ${payload.name}!</h1>
<p>You signed up 3 days ago — how is everything going?</p>
<p>Need help getting started? Reply to this email and we will help!</p>
`,
}),
});
if (!response.ok) {
throw new Error(`Follow-up email failed: ${await response.text()}`);
}
return { sent: true };
},
});This workflow demonstrates several powerful Trigger.dev features:
triggerAndWait()— triggers a child task and waits for it to complete, returning the resultPromise.all()— runs independent tasks in parallel for better performancetrigger()withdelay— schedules a task to run in the future (3 days later)- Workflow orchestration — one parent task coordinates multiple child tasks
Step 7: Add a Scheduled Cron Job
Create src/trigger/daily-digest.ts for a daily admin digest:
import { schedules, logger } from "@trigger.dev/sdk/v3";
export const dailySignupDigest = schedules.task({
id: "daily-signup-digest",
cron: "0 9 * * *", // Every day at 9:00 AM UTC
run: async () => {
logger.info("Running daily signup digest");
// Fetch yesterday's signups from your database
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
// Replace with your actual database query
const newUsers = await fetchNewUsers(yesterday);
if (newUsers.length === 0) {
logger.info("No new signups yesterday");
return { sent: false, reason: "no_new_signups" };
}
// Send digest email to admin
const response = await fetch("https://api.resend.com/emails", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.RESEND_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
from: "reports@yourapp.com",
to: "admin@yourapp.com",
subject: `Daily Signup Digest — ${newUsers.length} new users`,
html: `
<h1>Daily Signup Report</h1>
<p>${newUsers.length} new users signed up yesterday:</p>
<table>
<tr><th>Name</th><th>Email</th><th>Time</th></tr>
${newUsers
.map(
(u) =>
`<tr><td>${u.name}</td><td>${u.email}</td><td>${u.createdAt}</td></tr>`
)
.join("")}
</table>
`,
}),
});
if (!response.ok) {
throw new Error(`Digest email failed: ${await response.text()}`);
}
logger.info("Digest sent", { userCount: newUsers.length });
return { sent: true, userCount: newUsers.length };
},
});
// Mock function — replace with your database query
async function fetchNewUsers(since: Date) {
// Example: Prisma, Drizzle, or raw SQL
// return await db.user.findMany({ where: { createdAt: { gte: since } } });
return [
{
name: "Alice",
email: "alice@example.com",
createdAt: since.toISOString(),
},
];
}The schedules.task() function creates a cron-triggered task. The cron expression 0 9 * * * means "every day at 9:00 AM UTC". You can use any standard cron expression.
Step 8: Trigger Tasks from Next.js
Now wire the workflow into your Next.js application. Create an API route at src/app/api/signup/route.ts:
import { NextRequest, NextResponse } from "next/server";
import { tasks } from "@trigger.dev/sdk/v3";
import type { onboardingWorkflow } from "@/trigger/onboarding-workflow";
export async function POST(request: NextRequest) {
const body = await request.json();
const { email, name, userId } = body;
if (!email || !name || !userId) {
return NextResponse.json(
{ error: "Missing required fields" },
{ status: 400 }
);
}
// Trigger the onboarding workflow — returns immediately
const handle = await tasks.trigger<typeof onboardingWorkflow>(
"onboarding-workflow",
{
userId,
email,
name,
}
);
return NextResponse.json({
message: "Signup successful! Onboarding started.",
runId: handle.id,
});
}Key insight: tasks.trigger() returns immediately — it does not wait for the background job to complete. The user gets an instant response while the onboarding workflow runs asynchronously. The handle.id can be used to check the status later.
You can also trigger tasks from Server Actions. Create src/app/actions.ts:
"use server";
import { tasks } from "@trigger.dev/sdk/v3";
import type { onboardingWorkflow } from "@/trigger/onboarding-workflow";
export async function signupAction(formData: FormData) {
const email = formData.get("email") as string;
const name = formData.get("name") as string;
const userId = crypto.randomUUID();
const handle = await tasks.trigger<typeof onboardingWorkflow>(
"onboarding-workflow",
{ userId, email, name }
);
return { success: true, runId: handle.id };
}Step 9: Build a Simple Signup Form
Create src/app/page.tsx:
"use client";
import { useState } from "react";
import { signupAction } from "./actions";
export default function SignupPage() {
const [status, setStatus] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
async function handleSubmit(formData: FormData) {
setLoading(true);
try {
const result = await signupAction(formData);
setStatus(
`Signed up successfully! Onboarding run: ${result.runId}`
);
} catch (error) {
setStatus("Something went wrong. Please try again.");
} finally {
setLoading(false);
}
}
return (
<main className="flex min-h-screen items-center justify-center bg-gray-50">
<div className="w-full max-w-md rounded-lg bg-white p-8 shadow-md">
<h1 className="mb-6 text-2xl font-bold text-gray-900">
Sign Up
</h1>
<form action={handleSubmit} className="space-y-4">
<div>
<label
htmlFor="name"
className="block text-sm font-medium text-gray-700"
>
Name
</label>
<input
type="text"
id="name"
name="name"
required
className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 shadow-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
/>
</div>
<div>
<label
htmlFor="email"
className="block text-sm font-medium text-gray-700"
>
Email
</label>
<input
type="email"
id="email"
name="email"
required
className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 shadow-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
/>
</div>
<button
type="submit"
disabled={loading}
className="w-full rounded-md bg-blue-600 px-4 py-2 text-white hover:bg-blue-700 disabled:opacity-50"
>
{loading ? "Signing up..." : "Sign Up"}
</button>
</form>
{status && (
<p className="mt-4 rounded bg-green-50 p-3 text-sm text-green-700">
{status}
</p>
)}
</div>
</main>
);
}Step 10: Run the Development Server
Start both the Next.js dev server and the Trigger.dev dev worker:
# Terminal 1: Next.js
npm run dev
# Terminal 2: Trigger.dev worker
npx trigger.dev@latest devThe Trigger.dev dev command connects to the Trigger.dev cloud and runs your tasks locally. Open the Trigger.dev dashboard to see real-time logs, task runs, and errors.
Now test the flow:
- Open your app at
http://localhost:3000 - Fill in the signup form and submit
- Watch the Trigger.dev dashboard — you will see the onboarding workflow start, the welcome email and avatar tasks run in parallel, then the CRM sync, and finally the follow-up email scheduled for 3 days later
Step 11: Error Handling and Retry Strategies
Trigger.dev provides granular control over error handling. Let us enhance the welcome email task with custom error handling:
import { task, logger, retry } from "@trigger.dev/sdk/v3";
export const sendWelcomeEmailV2 = task({
id: "send-welcome-email-v2",
retry: {
maxAttempts: 5,
minTimeoutInMs: 1000,
maxTimeoutInMs: 60000,
factor: 2,
},
onFailure: async (payload, error, params) => {
// Called after ALL retries are exhausted
logger.error("Welcome email permanently failed", {
userId: payload.userId,
error: error.message,
attempts: params.attemptCount,
});
// Send to a dead-letter queue, alert on Slack, etc.
await fetch(process.env.SLACK_WEBHOOK_URL!, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
text: `Failed to send welcome email to ${payload.email} after ${params.attemptCount} attempts: ${error.message}`,
}),
});
},
run: async (payload: WelcomeEmailPayload) => {
// Task implementation...
},
});The onFailure callback runs only after all retries are exhausted — it is your last chance to handle the failure (alert on Slack, write to a dead-letter queue, etc.).
You can also use retry.onThrow for fine-grained retry control within a task:
run: async (payload) => {
// Only retry this specific operation, not the entire task
const result = await retry.onThrow(
async () => {
const res = await fetch("https://flaky-api.com/data");
if (!res.ok) throw new Error("API call failed");
return res.json();
},
{ maxAttempts: 3, randomize: true }
);
// Continue with the result...
logger.info("Got data", { result });
};Step 12: Add Task Metadata and Tags
Tags and metadata make tasks searchable and filterable in the dashboard:
import { task, logger, metadata, tags } from "@trigger.dev/sdk/v3";
export const processOrder = task({
id: "process-order",
run: async (payload: { orderId: string; userId: string; amount: number }) => {
// Add tags for filtering in the dashboard
tags.add("order", payload.orderId);
tags.add("user", payload.userId);
// Add metadata that appears in the run details
metadata.set("orderAmount", payload.amount);
metadata.set("currency", "USD");
// Update progress
metadata.set("status", "processing");
// ... process the order ...
metadata.set("status", "completed");
return { processed: true };
},
});Step 13: Batch Operations with batchTrigger
When you need to process multiple items, use batch triggering:
import { tasks } from "@trigger.dev/sdk/v3";
import type { sendWelcomeEmail } from "@/trigger/welcome-email";
// Send emails to multiple users at once
export async function sendBulkEmails(
users: Array<{ userId: string; email: string; name: string }>
) {
const handle = await tasks.batchTrigger<typeof sendWelcomeEmail>(
"send-welcome-email",
users.map((user) => ({
payload: user,
}))
);
return {
batchId: handle.batchId,
runCount: handle.runs.length,
};
}Trigger.dev processes batch items with controlled concurrency, preventing you from overwhelming downstream services.
Step 14: Environment Variables and Configuration
Add your environment variables to .env.local:
# Trigger.dev
TRIGGER_SECRET_KEY=tr_dev_your_secret_key
# Email (Resend)
RESEND_API_KEY=re_your_api_key
# CRM
CRM_API_URL=https://api.your-crm.com
CRM_API_KEY=your_crm_key
# Notifications
SLACK_WEBHOOK_URL=https://hooks.slack.com/services/your/webhookFor production, add these same variables in the Trigger.dev dashboard under your project settings. Environment variables configured there are injected into your tasks at runtime on the Trigger.dev cloud.
Step 15: Deploy to Production
Deploy your tasks to the Trigger.dev cloud:
npx trigger.dev@latest deployThis command:
- Bundles your task code
- Uploads it to Trigger.dev cloud
- Makes your tasks available for production triggering
Your Next.js app triggers tasks via the SDK, and Trigger.dev cloud executes them on managed infrastructure. No servers to provision, no workers to manage.
For CI/CD integration, add the deploy command to your pipeline:
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
jobs:
deploy-trigger:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npx trigger.dev@latest deploy
env:
TRIGGER_ACCESS_TOKEN: ${{ secrets.TRIGGER_ACCESS_TOKEN }}
deploy-nextjs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci && npm run build
# Deploy to Vercel, Railway, etc.Step 16: Monitor and Debug
The Trigger.dev dashboard gives you full observability:
- Runs list — see all task executions with status, duration, and timestamps
- Run details — view logs, payload, output, and retry history for each run
- Workflow trace — visualize multi-step workflows with parent-child task relationships
- Errors — filter and search failed runs with full stack traces
- Schedules — manage and monitor cron jobs
Use structured logging liberally — every logger.info(), logger.warn(), and logger.error() call appears in the run details with its metadata.
Testing Your Implementation
To verify everything works:
- Start the dev server and Trigger.dev worker in two terminals
- Submit the signup form — you should see an instant response
- Check the Trigger.dev dashboard — the onboarding workflow should show:
- Welcome email and avatar generation running in parallel
- CRM sync running after both complete
- Follow-up email scheduled for 3 days later
- Test error handling — temporarily break an API key and verify retries work
- Check the cron job — it should appear in the Schedules tab, set to run daily at 9 AM UTC
Troubleshooting
Tasks not appearing in the dashboard:
Make sure npx trigger.dev@latest dev is running. Check that your trigger.config.ts has the correct dirs pointing to your task files.
Tasks failing with "Module not found":
Trigger.dev bundles tasks separately from Next.js. Ensure all imports in task files are self-contained and do not import from Next.js-specific modules (like next/server).
Retry not working in development:
Set enabledInDev: true in your trigger.config.ts retries configuration.
Environment variables not available in tasks:
In development, tasks read from .env.local. In production, configure environment variables in the Trigger.dev dashboard.
Project Structure
Here is the final project structure:
trigger-onboarding/
src/
app/
api/
signup/
route.ts # API route to trigger onboarding
actions.ts # Server Action for form submission
page.tsx # Signup form UI
trigger/
welcome-email.ts # Welcome email task
generate-avatar.ts # Avatar generation task
sync-crm.ts # CRM sync task
onboarding-workflow.ts # Multi-step workflow orchestrator
daily-digest.ts # Scheduled cron job
trigger.config.ts # Trigger.dev configuration
.env.local # Environment variables
Next Steps
Now that you have a working background job system, consider:
- Adding more workflows — order processing, report generation, data pipelines
- Implementing idempotency — use unique keys to prevent duplicate task execution
- Setting up alerting — configure Slack or email notifications for failed tasks
- Using Trigger.dev with a database — combine with Prisma or Drizzle for data persistence
- Exploring fan-out patterns — use
batchTriggerAndWaitfor processing large datasets - Self-hosting — Trigger.dev v3 can be self-hosted if you need to run on your own infrastructure
Conclusion
Trigger.dev v3 brings background job processing to the TypeScript ecosystem with a developer experience that feels native to Next.js. Instead of managing Redis queues, worker processes, and retry logic yourself, you write plain TypeScript functions and let Trigger.dev handle the infrastructure.
In this tutorial, you built a complete user onboarding automation system with parallel task execution, multi-step workflows, scheduled cron jobs, error handling with retries, and production deployment. The same patterns apply to any background processing need — from sending emails to processing payments to running AI pipelines.
The key takeaways are:
- Tasks are just TypeScript functions — no special syntax or DSL to learn
triggerAndWaitenables workflow orchestration — compose complex workflows from simple tasks- Retries and error handling are built in — configure once, handle failures gracefully
- Scheduled tasks replace cron servers — no infrastructure to manage for recurring jobs
- The dashboard provides full observability — debug production issues with logs, traces, and metadata
Discuss Your Project with Us
We're here to help with your web development needs. Schedule a call to discuss your project and how we can assist you.
Let's find the best solutions for your needs.
Related Articles

Upstash Redis and Next.js: Rate Limiting, Caching, and Message Queues
Learn how to integrate Upstash Redis into a Next.js application to implement rate limiting, server-side caching, and message queues. This comprehensive tutorial covers setup, production patterns, and serverless deployment.

Building an Autonomous AI Agent with Agentic RAG and Next.js
Learn how to build an AI agent that autonomously decides when and how to retrieve information from vector databases. A comprehensive hands-on guide using Vercel AI SDK and Next.js with executable examples.

Better Auth with Next.js 15: The Complete Authentication Guide for 2026
Learn how to implement full-featured authentication in Next.js 15 using Better Auth. This tutorial covers email/password, OAuth, sessions, middleware protection, and role-based access control.