Integrating powerful backend services with a dynamic frontend is a cornerstone of modern web application development. In this chapter, we’ll connect the dots between your Next.js application, robust TypeScript-powered Trigger.dev workflows, and external APIs. This combination allows you to offload heavy computations, long-running tasks, and complex integrations to Trigger.dev, keeping your frontend responsive and scalable.
You’ll learn how to invoke Trigger.dev jobs from your Next.js application, define these jobs with the clarity and safety of TypeScript, and make secure, resilient calls to third-party APIs from within your workflows. This approach is fundamental for building features like automated data synchronization, background processing, and AI-driven responses without blocking your user interface. We’ll be using Trigger.dev v4-beta, which is the latest iteration, with v3 being the current stable release. Version 4 is expected to go GA around May/June 2026.
Before diving in, ensure you have a basic understanding of Next.js fundamentals, including API routes, and have completed the initial Trigger.dev project setup from previous chapters. We’ll be building upon that foundation to create a truly integrated system.
Trigger.dev in a Modern Web Stack
When building applications with frameworks like Next.js, you often encounter tasks that are too long-running, too resource-intensive, or too sensitive to execute directly on the frontend or within a quick API response. This is where Trigger.dev shines, acting as a reliable orchestrator for these background operations.
The Frontend-Backend Workflow Bridge
Imagine a user submits a form that requires calling multiple external APIs, processing data, and then sending an email. Doing all of this synchronously in a Next.js API route could lead to timeouts, poor user experience, and difficult error handling.
This is where Trigger.dev steps in. Your Next.js application (specifically, an API route) can act as a lightweight trigger, initiating a Trigger.dev workflow. The workflow then handles all the heavy lifting in the background, allowing your Next.js API route to respond quickly to the user.
📌 Key Idea: Trigger.dev decouples the request from heavy processing, making your frontend responsive and your backend resilient. It handles the complexity of background jobs, retries, and durable execution.
Why TypeScript for Trigger.dev Workflows?
TypeScript brings static typing to JavaScript, offering significant advantages, especially for complex and long-running workflows that often involve diverse data structures and external integrations:
- Type Safety at Compile Time: Define the expected structure of your job payloads, outputs, and any data passed between steps. This catches many common programming errors related to data mismatches before your code even runs, preventing runtime surprises.
- Enhanced Developer Experience: Your Integrated Development Environment (IDE) can provide intelligent autocompletion, real-time error checking, and assist with refactoring. This makes development faster, more confident, and less prone to errors.
- Improved Code Readability and Maintainability: Clearly defined types act as a form of self-documentation. It becomes easier for you and your team to understand what data a workflow expects, what it produces, and how different parts of the system interact.
- Reduced Bugs in Production: By enforcing strict type contracts, TypeScript helps prevent unexpected data formats or missing properties from causing issues deeper within your workflow or when interacting with external systems. This is particularly crucial for long-running processes where debugging can be more challenging.
For production systems, TypeScript is almost a necessity for maintaining code quality, predictability, and team collaboration.
Securely Interacting with External APIs
Trigger.dev workflows are an ideal place to interact with external APIs because they run in a secure, server-side environment. This allows you to:
- Protect Sensitive Credentials: Store API keys, tokens, and other sensitive credentials securely as environment variables or using a secrets manager. These are never exposed to the client-side, mitigating security risks.
- Implement Robust Retries and Backoff: External APIs can be flaky. Trigger.dev’s durable execution automatically retries API calls that fail due to transient network issues, timeouts, or temporary service unavailability. You can configure exponential backoff strategies to prevent overwhelming the external service.
- Manage API Rate Limits: Implement logic to respect API rate limits, potentially pausing or delaying subsequent calls using Trigger.dev’s built-in delay functions, without impacting your frontend’s responsiveness or blocking other operations.
- Transform and Validate Data: Process, validate, and transform data received from external APIs before it’s used elsewhere or stored. This ensures data consistency and integrity within your application.
⚡ Real-world insight: Never expose API keys directly in client-side code. Always route API calls through a secure backend or a service like Trigger.dev to protect your credentials and manage API interactions robustly.
Step-by-Step Implementation
Let’s build a simple Next.js application that triggers a Trigger.dev job. This job will then call a public external API and return a processed result.
Prerequisites
Before starting this section, ensure you have a Trigger.dev project set up and running, as discussed in previous chapters. This typically involves a separate Node.js project where your Trigger.dev jobs are defined and run by the Trigger.dev CLI.
1. Set Up a Next.js Project
If you don’t have one already, create a new Next.js project. We’ll use the App Router for modern Next.js development, along with TypeScript.
npx create-next-app@latest my-trigger-app --typescript --app --tailwind --eslint
When prompted, choose your preferred options. For simplicity, we’ll stick with the defaults. Once created, navigate into your new project directory:
cd my-trigger-app
2. Install Trigger.dev Client in Next.js
Inside your Next.js project, install the @trigger.dev/sdk client library. This package allows your frontend or API routes to communicate with your Trigger.dev workflows by sending events.
npm install @trigger.dev/sdk
# or yarn add @trigger.dev/sdk
3. Integrate Trigger.dev Client and Create an API Route
We’ll create a Next.js API route that receives a request from the frontend and then triggers a Trigger.dev job.
First, create a triggerClient.ts file to initialize your Trigger.dev client. This ensures you only initialize it once and provides a single point of configuration.
Create src/lib/triggerClient.ts within your Next.js project:
// src/lib/triggerClient.ts
import { TriggerClient } from "@trigger.dev/sdk";
// Initialize the Trigger.dev client for sending events.
// The 'id' should be a unique identifier for your application.
// The 'apiKey' is your Trigger.dev API key, kept secret.
// 'apiUrl' is optional, defaults to the public Trigger.dev API.
export const client = new TriggerClient({
id: "my-nextjs-app", // A unique ID for your application connecting to Trigger.dev
apiKey: process.env.TRIGGER_API_KEY, // Your secret API key
apiUrl: process.env.TRIGGER_PUBLIC_API_URL || "https://api.trigger.dev",
});
Next, create an API route that will be responsible for triggering a job. This route will receive data from your frontend and forward it as a payload to a Trigger.dev event.
Create src/app/api/trigger-job/route.ts:
// src/app/api/trigger-job/route.ts
import { NextResponse } from "next/server";
import { client } from "@/lib/triggerClient"; // Import our initialized Trigger.dev client
// Define the job's ID - this must match the ID defined in your Trigger.dev workflow.
const MY_JOB_ID = "process-public-api-data";
export async function POST(request: Request) {
try {
// Parse the incoming JSON request body
const { message } = await request.json();
// Basic validation for the 'message' field
if (!message) {
return NextResponse.json({ error: "Message is required" }, { status: 400 });
}
// Trigger the Trigger.dev job by sending an event.
// The 'name' property must match the 'name' in the eventTrigger of your job definition.
// The 'payload' is the data you want to send to your job.
const jobRun = await client.sendEvent({
name: MY_JOB_ID, // The name of the event that triggers your job
payload: { userInput: message, timestamp: new Date().toISOString() }, // Data sent to the job
});
console.log(`Triggered job run: ${jobRun.id}`);
// Respond to the frontend indicating success and providing the job run ID
return NextResponse.json({
success: true,
jobRunId: jobRun.id,
message: "Job successfully triggered!",
});
} catch (error) {
console.error("Error triggering job:", error);
// Return an error response if something goes wrong
return NextResponse.json(
{ error: "Failed to trigger job", details: (error as Error).message },
{ status: 500 }
);
}
}
Finally, you need to add your Trigger.dev API Key to your Next.js project’s environment variables. Create a .env.local file at the root of your Next.js project:
# .env.local (in your Next.js project root)
TRIGGER_API_KEY=tr_dev_YOUR_SECRET_KEY_HERE
TRIGGER_PUBLIC_API_URL=https://api.trigger.dev # Optional: defaults to the public API
Important: Replace tr_dev_YOUR_SECRET_KEY_HERE with your actual Trigger.dev API key, which you can find in your Trigger.dev dashboard. For production deployment, ensure these environment variables are configured in your hosting provider (e.g., Vercel, Netlify).
4. Create a TypeScript Workflow in Trigger.dev
Now, let’s define the actual Trigger.dev job that will be executed. This will reside in your Trigger.dev project (which you should have set up in previous chapters, likely in a src/jobs directory).
First, ensure you have axios installed in your Trigger.dev project to make HTTP requests:
# Navigate to your Trigger.dev project directory
cd path/to/your/trigger-dev-project
npm install axios
# or yarn add axios
Next, create src/jobs/process-public-api-data.ts in your Trigger.dev project:
// src/jobs/process-public-api-data.ts
import { client } from "@/trigger"; // Assuming your Trigger.dev client is initialized here (e.g., in src/trigger.ts)
import { eventTrigger } from "@trigger.dev/sdk";
import axios from "axios"; // For making HTTP requests in the job
// Define the input type for our job payload, leveraging TypeScript for safety.
// This type must match the 'payload' structure sent from the Next.js API route.
interface ProcessApiPayload {
userInput: string;
timestamp: string;
}
// Define the output type for our job, which will be the result of the 'run' function.
interface ProcessApiResult {
originalInput: string;
processedData: any; // In a real app, define a more specific type for your API response
externalApiUrl: string;
jobRunId: string;
}
client.defineJob({
id: "process-public-api-data", // This ID MUST match the MY_JOB_ID in your Next.js API route
name: "Process Public API Data",
version: "1.0.0",
enabled: true,
// This job is triggered by an event. The 'name' property here
// must match the 'name' in client.sendEvent() from your Next.js app.
trigger: eventTrigger({
name: "process-public-api-data",
schema: {
// Define the JSON schema for the incoming payload.
// This provides runtime validation and TypeScript inference.
type: "object",
properties: {
userInput: { type: "string", description: "User provided search term" },
timestamp: { type: "string", format: "date-time", description: "Time of event trigger" },
},
required: ["userInput", "timestamp"],
additionalProperties: false, // Disallow unexpected properties
},
}),
// The 'run' function contains the core logic of your job.
// 'payload' will be type-checked against ProcessApiPayload.
// '{ logger, id: jobRunId }' provides logging and the unique ID for the current job run.
run: async (payload: ProcessApiPayload, { logger, id: jobRunId }) => {
logger.info("Starting job to process public API data...", { payload });
const externalApiUrl = "https://api.publicapis.org/entries"; // A simple public API for demonstration
try {
// Step 1: Call an external API using axios.
logger.info(`Calling external API: ${externalApiUrl}`);
const response = await axios.get(externalApiUrl, {
params: { title: payload.userInput }, // Use user input as a query parameter
timeout: 10000, // 10 seconds timeout for the API call
});
// Step 2: Process the API response.
// We'll take the top 3 entries from the response for simplicity.
const processedData = response.data.entries ? response.data.entries.slice(0, 3) : [];
logger.info("Successfully fetched and processed data from external API.", {
count: processedData.length,
});
// Step 3: Return the result, adhering to our ProcessApiResult type.
const result: ProcessApiResult = {
originalInput: payload.userInput,
processedData: processedData,
externalApiUrl: externalApiUrl,
jobRunId: jobRunId,
};
logger.info("Job completed successfully.", { result });
return result; // This result will be visible in the Trigger.dev dashboard
} catch (error) {
logger.error("Failed to call or process external API.", { error: (error as Error).message });
// Throwing an error will cause Trigger.dev to potentially retry the job,
// depending on your job's retry configuration.
throw new Error(`External API call failed: ${(error as Error).message}`);
}
},
});
5. Create a Frontend Component to Trigger the Job
Let’s create a simple form in your Next.js application that calls our API route, which in turn triggers the Trigger.dev job.
Open src/app/page.tsx in your Next.js project and replace its content with the following:
// src/app/page.tsx
"use client"; // This component needs to be a Client Component to use hooks like useState
import { useState } from "react";
export default function Home() {
const [message, setMessage] = useState("");
const [response, setResponse] = useState<any>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
// Handles the form submission
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault(); // Prevent default form submission behavior
setLoading(true); // Indicate loading state
setResponse(null); // Clear previous response
setError(null); // Clear previous errors
try {
// Make a POST request to our Next.js API route
const res = await fetch("/api/trigger-job", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ message }), // Send the user's input as JSON
});
const data = await res.json(); // Parse the JSON response from our API route
// Check if the API response was not OK (e.g., status 400 or 500)
if (!res.ok) {
throw new Error(data.details || data.error || "Something went wrong");
}
setResponse(data); // Set the successful response
} catch (err) {
setError((err as Error).message); // Catch and display any errors
} finally {
setLoading(false); // End loading state
}
};
return (
<main className="flex min-h-screen flex-col items-center justify-center p-24 bg-gray-100">
<h1 className="text-4xl font-bold mb-8 text-gray-800">Trigger.dev with Next.js & APIs</h1>
<form onSubmit={handleSubmit} className="bg-white p-8 rounded-lg shadow-md w-full max-w-md">
<div className="mb-4">
<label htmlFor="message" className="block text-gray-700 text-sm font-bold mb-2">
Enter a search term for public APIs:
</label>
<input
type="text"
id="message"
className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
value={message}
onChange={(e) => setMessage(e.target.value)}
placeholder="e.g., animals, health, food"
required
/>
</div>
<button
type="submit"
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline disabled:opacity-50"
disabled={loading} // Disable button while loading
>
{loading ? "Triggering..." : "Trigger Trigger.dev Job"}
</button>
</form>
{loading && <p className="mt-4 text-blue-600">Loading...</p>}
{error && <p className="mt-4 text-red-600">Error: {error}</p>}
{response && (
<div className="mt-8 bg-green-100 border-l-4 border-green-500 text-green-700 p-4 w-full max-w-md" role="alert">
<p className="font-bold">Job Triggered Successfully!</p>
<p>Job Run ID: <code className="font-mono">{response.jobRunId}</code></p>
<p>Message: {response.message}</p>
<p className="mt-2">Check your Trigger.dev dashboard for job progress and results!</p>
</div>
)}
</main>
);
}
6. Run Your Applications
Now that both your Next.js application and Trigger.dev project are configured, let’s bring them to life!
Start your Trigger.dev project: Open your terminal, navigate to your Trigger.dev project directory, and start the development server. This connects your local Trigger.dev worker to the Trigger.dev cloud.
# In your Trigger.dev project directory npm run devYou should see output indicating that your Trigger.dev project is running and listening for events from the cloud.
Start your Next.js application: Open a separate terminal, navigate to your Next.js project directory (
my-trigger-app), and start the Next.js development server.# In your Next.js project directory npm run devOpen your browser to
http://localhost:3000.
Now, enter a search term in the Next.js form (e.g., “animals”, “health”, “food”) and submit it. You should see a “Job Triggered Successfully!” message with a jobRunId. Go to your Trigger.dev dashboard (usually http://localhost:8080 if self-hosting, or your cloud dashboard), and you’ll see a new run for the “Process Public API Data” job, showing its progress and eventual result, including the data fetched from the external API.
Mini-Challenge: Enhance the API Call
Your turn! Let’s make the external API interaction a bit more dynamic and give the user more control.
- Challenge: Modify the
process-public-api-data.tsTrigger.dev job to allow the user to specify which public API to call (e.g.,https://api.publicapis.org/entriesorhttps://catfact.ninja/fact).- Update the
ProcessApiPayloadTypeScript interface to include anapiEndpointfield (e.g.,apiEndpoint: string;). - Modify the
eventTriggerschema in your Trigger.dev job to include theapiEndpointproperty. - Adjust the Next.js frontend (
src/app/page.tsx) to include a new input field (like a text input or dropdown) for theapiEndpoint. - Modify the Trigger.dev job’s
runfunction to use theapiEndpointfrom the payload for itsaxios.getcall.
- Update the
- Hint: Remember to handle potential errors gracefully if the provided
apiEndpointis invalid or inaccessible. You might want to add a default API endpoint in your job as a fallback. - What to observe/learn: How to make your Trigger.dev workflows more flexible and configurable through dynamic inputs, and how TypeScript helps maintain type safety even with changing data structures.
Common Pitfalls & Troubleshooting
Working with integrated systems like Next.js and Trigger.dev can introduce a few common challenges. Here’s how to debug them effectively.
⚠️ What can go wrong: Environment Variable Mismatches
- Issue: Your Trigger.dev API key (
TRIGGER_API_KEY) or other secrets are not correctly loaded, leading to401 Unauthorizederrors when your Next.js app tries to trigger jobs, or your Trigger.dev worker cannot connect. - Troubleshooting:
- Local Development (Next.js): Ensure
TRIGGER_API_KEYis present in your.env.localfile at the root of your Next.js project. Remember to restart your Next.js dev server (npm run dev) after changing.env.local, as Next.js caches these variables. - Deployment (Next.js): Verify that your hosting provider (e.g., Vercel, Netlify) has the
TRIGGER_API_KEYenvironment variable correctly configured for your Next.js application. It’s often set in the dashboard settings. - Trigger.dev Project: Similarly, ensure your Trigger.dev project has its
TR_API_KEY(or the equivalent variable you used to initialize itsTriggerClient) correctly set for its environment, both locally and in deployment.
- Local Development (Next.js): Ensure
⚠️ What can go wrong: TypeScript Configuration Errors
- Issue: You encounter compilation errors related to types (e.g., “Property ‘x’ does not exist on type ‘Y’”). This often happens when the expected data structure doesn’t match the actual data.
- Troubleshooting:
tsconfig.json: Ensure yourtsconfig.jsonfiles in both your Next.js and Trigger.dev projects are correctly configured. Pay special attention tobaseUrlandpathsif you’re using absolute imports (like@/lib/triggerClient).- Interface/Type Mismatches: Double-check that the
payloadyou are sending from Next.js (defined byclient.sendEvent’spayload) exactly matches theschemadefined ineventTriggerand theinterface(ProcessApiPayload) used in your Trigger.dev job. TypeScript helps catch this early, but if you change one without the other, it will break. - Missing Type Definitions: Ensure all necessary type definition packages are installed (e.g.,
@types/axiosif you’re usingaxiosin a TypeScript project).
⚠️ What can go wrong: External API Rate Limits or Failures
- Issue: Your Trigger.dev jobs are failing due to frequent
429 Too Many Requests(rate limit) or5xx(server error) responses from the external API. - Troubleshooting:
- Trigger.dev Retries (Default): By default, Trigger.dev automatically retries jobs on unhandled exceptions. This is your first line of defense against transient failures.
- Explicit Retries: For more fine-grained control, you can use Trigger.dev’s retry options directly in your
runfunction ordefineJobconfiguration. This allows you to specify maximum attempts, backoff strategies, and specific error codes to retry.// Example of explicit retries for an API call within a job step // (This is an illustrative example; Trigger.dev's SDK provides dedicated retry APIs) client.defineJob({ // ... run: async (payload, { logger }) => { // ... // Use Trigger.dev's built-in retry mechanism for a specific step const apiResult = await client.retries.add( "call-external-api", // A unique ID for this retryable step { maxAttempts: 5, minTimeoutInMs: 1000, // Start with 1 second delay maxTimeoutInMs: 30000, // Max delay of 30 seconds factor: 2, // Exponential backoff (1s, 2s, 4s, 8s, 16s...) // Other retry options like 'randomize', 'retryOn' (specific status codes) }, async () => { logger.info("Attempting external API call..."); const response = await axios.get(externalApiUrl); return response.data; } ); // ... } }); - Rate Limit Headers: Many APIs include
Retry-Afterheaders in their429responses. You can read these headers in your job and useclient.delays.delayUntil()to pause the job until the specified time, effectively respecting the API’s instructions. - Detailed Error Logging: Always use
logger.error()in your job to log detailed API error responses (e.g., status codes, error messages from the external API). This helps understand if the problem is transient or requires code changes.
Summary
This chapter has guided you through the essential process of integrating Trigger.dev into a modern web application stack. You’ve learned:
- Next.js as a Trigger: How to use Next.js API routes to securely and efficiently trigger Trigger.dev jobs, effectively offloading complex and long-running tasks from your frontend and immediate API responses.
- TypeScript for Robustness: The significant benefits of using TypeScript to define clear types for your job payloads and outputs, leading to more maintainable, readable, and less error-prone workflows.
- External API Interaction: Best practices for calling external APIs from within Trigger.dev jobs, including securely handling secrets, implementing robust retry mechanisms, and managing API rate limits.
By mastering these integrations, you’re now equipped to build more dynamic, scalable, and resilient applications. The ability to delegate background tasks to a durable execution platform like Trigger.dev, combined with the type safety of TypeScript, empowers you to focus on core application logic rather than infrastructure concerns.
Next, we’ll delve deeper into Trigger.dev’s advanced features, exploring how to manage long-running workflows and introduce human-in-the-loop interactions for even more powerful and flexible systems.
References
- Trigger.dev Documentation
- Trigger.dev GitHub Repository
- Next.js Documentation
- TypeScript Handbook
- Axios GitHub Repository
This page is AI-assisted and reviewed. It references official documentation and recognized resources where relevant.