Announcing our new Vercel integration
Join our Discord
Sign up for free

Next.js

Inngest works with NextJS out of the box, and allows you to write background jobs, scheduled functions, and event driven systems using Next's API routes.

Follow along to learn how to:

  1. Integrate your event types within functions
  2. Write functions in NextJS
  3. Serve your functions with a new API route
  4. Register your functions with Inngest after deploying to your hosting service.

Integrating event types into your project

Inngest fully types every event you send, ensuring that your functions and data are always valid. To generate your type library, run:

npx inngest-cli types ts

This will prompt you to log in to your Inngest account, then generate types for every event in your account (plus public events) inside the __generated__ folder.

Writing functions in NextJS

Firstly, create a new directory within the root of your NextJS app called inngest. This will contain all of your functions:

bash
mkdir ./inngest

Within this directory you can write your Inngest functions, eg ./inngest/new_pr.ts:

typescript
// @filename: ../__generated__/inngest.ts
export const Action = {
OPENED: "opened",
CLOSED: "closed",
MERGED: "merged",
REVIEW_REQUESTED: "review_requested",
SYNCHRONIZE: "synchronize",
EDITED: "edited",
} as const;
export type Action = typeof Action[keyof typeof Action];
export type GithubPullRequest = {
name: "github/pull_request";
data: {
action: Action;
organization: {
description: string;
events_url: string;
login: string;
public_members_url: string;
repos_url: string;
url: string;
avatar_url: string;
id: number;
issues_url: string;
members_url: string;
node_id: string;
hooks_url: string;
};
pull_request: {
diff_url: string;
labels: Array<unknown>;
title: string;
body: string;
closed_at: unknown;
deletions: number;
commits_url: string;
merged_at: unknown;
statuses_url: string;
user: {
events_url: string;
node_id: string;
organizations_url: string;
type: string;
url: string;
following_url: string;
gists_url: string;
html_url: string;
repos_url: string;
followers_url: string;
id: number;
site_admin: boolean;
starred_url: string;
subscriptions_url: string;
avatar_url: string;
gravatar_id: string;
login: string;
received_events_url: string;
};
author_association: string;
base: {
label: string;
ref: string;
repo: {
branches_url: string;
name: string;
subscribers_url: string;
svn_url: string;
topics: Array<unknown>;
allow_merge_commit: boolean;
git_url: string;
releases_url: string;
assignees_url: string;
events_url: string;
full_name: string;
private: boolean;
trees_url: string;
updated_at: string;
watchers_count: number;
allow_rebase_merge: boolean;
issue_comment_url: string;
issue_events_url: string;
milestones_url: string;
watchers: number;
disabled: boolean;
downloads_url: string;
license: unknown;
merges_url: string;
teams_url: string;
allow_squash_merge: boolean;
collaborators_url: string;
commits_url: string;
contents_url: string;
languages_url: string;
mirror_url: unknown;
visibility: string;
allow_auto_merge: boolean;
archive_url: string;
has_downloads: boolean;
size: number;
ssh_url: string;
statuses_url: string;
allow_forking: boolean;
contributors_url: string;
default_branch: string;
fork: boolean;
forks_url: string;
git_refs_url: string;
keys_url: string;
subscription_url: string;
tags_url: string;
created_at: string;
forks_count: number;
has_wiki: boolean;
open_issues: number;
open_issues_count: number;
is_template: boolean;
allow_update_branch: boolean;
archived: boolean;
forks: number;
git_commits_url: string;
has_issues: boolean;
has_pages: boolean;
html_url: string;
issues_url: string;
blobs_url: string;
compare_url: string;
git_tags_url: string;
labels_url: string;
language: string;
delete_branch_on_merge: boolean;
notifications_url: string;
stargazers_count: number;
clone_url: string;
has_projects: boolean;
id: number;
pulls_url: string;
owner: {
node_id: string;
organizations_url: string;
repos_url: string;
events_url: string;
html_url: string;
login: string;
avatar_url: string;
type: string;
subscriptions_url: string;
following_url: string;
id: number;
received_events_url: string;
site_admin: boolean;
starred_url: string;
url: string;
followers_url: string;
gists_url: string;
gravatar_id: string;
};
comments_url: string;
description: string;
homepage: unknown;
pushed_at: string;
stargazers_url: string;
deployments_url: string;
hooks_url: string;
node_id: string;
url: string;
};
sha: string;
user: {
events_url: string;
followers_url: string;
following_url: string;
gravatar_id: string;
starred_url: string;
subscriptions_url: string;
site_admin: boolean;
type: string;
node_id: string;
organizations_url: string;
repos_url: string;
avatar_url: string;
gists_url: string;
html_url: string;
id: number;
login: string;
received_events_url: string;
url: string;
};
};
before?: string;
after?: string;
milestone: unknown;
node_id: string;
number: number;
requested_teams: Array<unknown>;
comments_url: string;
mergeable_state: string;
merged: boolean;
locked: boolean;
mergeable: unknown;
merged_by: unknown;
patch_url: string;
rebaseable: unknown;
active_lock_reason: unknown;
created_at: string;
head: {
label: string;
ref: string;
repo: {
pulls_url: string;
releases_url: string;
compare_url: string;
contributors_url: string;
git_commits_url: string;
issue_events_url: string;
license: unknown;
private: boolean;
updated_at: string;
url: string;
has_projects: boolean;
keys_url: string;
language: string;
notifications_url: string;
pushed_at: string;
size: number;
allow_auto_merge: boolean;
git_tags_url: string;
html_url: string;
id: number;
languages_url: string;
topics: Array<unknown>;
collaborators_url: string;
created_at: string;
has_downloads: boolean;
has_issues: boolean;
is_template: boolean;
name: string;
allow_forking: boolean;
commits_url: string;
contents_url: string;
default_branch: string;
forks: number;
owner: {
starred_url: string;
subscriptions_url: string;
type: string;
node_id: string;
site_admin: boolean;
organizations_url: string;
repos_url: string;
gists_url: string;
id: number;
events_url: string;
login: string;
following_url: string;
gravatar_id: string;
html_url: string;
received_events_url: string;
url: string;
avatar_url: string;
followers_url: string;
};
allow_merge_commit: boolean;
archived: boolean;
forks_url: string;
issues_url: string;
subscribers_url: string;
svn_url: string;
tags_url: string;
visibility: string;
allow_squash_merge: boolean;
milestones_url: string;
watchers: number;
comments_url: string;
delete_branch_on_merge: boolean;
git_url: string;
issue_comment_url: string;
statuses_url: string;
subscription_url: string;
deployments_url: string;
fork: boolean;
git_refs_url: string;
merges_url: string;
watchers_count: number;
assignees_url: string;
branches_url: string;
has_wiki: boolean;
allow_update_branch: boolean;
clone_url: string;
description: string;
open_issues: number;
stargazers_url: string;
trees_url: string;
allow_rebase_merge: boolean;
archive_url: string;
blobs_url: string;
full_name: string;
has_pages: boolean;
homepage: unknown;
disabled: boolean;
downloads_url: string;
events_url: string;
forks_count: number;
hooks_url: string;
open_issues_count: number;
mirror_url: unknown;
ssh_url: string;
stargazers_count: number;
teams_url: string;
labels_url: string;
node_id: string;
};
sha: string;
user: {
node_id: string;
organizations_url: string;
received_events_url: string;
url: string;
id: number;
repos_url: string;
login: string;
subscriptions_url: string;
type: string;
avatar_url: string;
events_url: string;
gravatar_id: string;
html_url: string;
starred_url: string;
followers_url: string;
following_url: string;
gists_url: string;
site_admin: boolean;
};
};
requested_reviewers: Array<unknown>;
assignee: unknown;
comments: number;
html_url: string;
review_comments_url: string;
state: string;
additions: number;
assignees: Array<unknown>;
auto_merge: unknown;
merge_commit_sha: unknown;
id: number;
review_comment_url: string;
review_comments: number;
updated_at: string;
url: string;
draft: boolean;
issue_url: string;
maintainer_can_modify: boolean;
};
repository: {
branches_url: string;
html_url: string;
mirror_url: unknown;
size: number;
topics: Array<unknown>;
forks_url: string;
has_issues: boolean;
has_wiki: boolean;
homepage: unknown;
stargazers_url: string;
trees_url: string;
updated_at: string;
compare_url: string;
downloads_url: string;
id: number;
git_url: string;
contributors_url: string;
disabled: boolean;
git_commits_url: string;
keys_url: string;
open_issues: number;
open_issues_count: number;
ssh_url: string;
subscribers_url: string;
collaborators_url: string;
comments_url: string;
fork: boolean;
git_tags_url: string;
node_id: string;
contents_url: string;
deployments_url: string;
notifications_url: string;
owner: {
login: string;
node_id: string;
repos_url: string;
site_admin: boolean;
url: string;
followers_url: string;
gravatar_id: string;
html_url: string;
id: number;
received_events_url: string;
starred_url: string;
events_url: string;
type: string;
avatar_url: string;
following_url: string;
gists_url: string;
organizations_url: string;
subscriptions_url: string;
};
releases_url: string;
stargazers_count: number;
blobs_url: string;
issue_events_url: string;
tags_url: string;
default_branch: string;
events_url: string;
hooks_url: string;
statuses_url: string;
forks: number;
has_downloads: boolean;
language: string;
subscription_url: string;
archived: boolean;
created_at: string;
has_pages: boolean;
merges_url: string;
pushed_at: string;
git_refs_url: string;
labels_url: string;
languages_url: string;
license: unknown;
milestones_url: string;
teams_url: string;
description: string;
private: boolean;
pulls_url: string;
svn_url: string;
visibility: string;
forks_count: number;
full_name: string;
is_template: boolean;
issues_url: string;
archive_url: string;
assignees_url: string;
commits_url: string;
has_projects: boolean;
watchers: number;
allow_forking: boolean;
clone_url: string;
issue_comment_url: string;
name: string;
url: string;
watchers_count: number;
};
sender: {
events_url: string;
gists_url: string;
login: string;
url: string;
followers_url: string;
following_url: string;
id: number;
site_admin: boolean;
subscriptions_url: string;
type: string;
html_url: string;
node_id: string;
avatar_url: string;
gravatar_id: string;
organizations_url: string;
received_events_url: string;
repos_url: string;
starred_url: string;
};
};
user: {};
v?: string;
ts?: number;
};
// @filename: index.ts
// ---cut---
import { createFunction } from "inngest";
// Import the type for the event you want to listen to. This fully types
// the arguments to your function. The types are generated by running
// `npx inngest-cli types ts`.
import { GithubPullRequest } from '../__generated__/inngest';
export const newPR = createFunction<GithubPullRequest>("New PR", "github/pull_request", ({ event }) => {
// This function is triggered when the `github/pull_request` event is received via
// a GH webhook.
if (event.data.action === 'opened') {
// New PR opened.
}
});

Serving functions in NextJS

Create a new ./pages/api/inngest.ts file to serve the Inngest function API. Import serve from the inngest/next package to create your endpoint, with all of the functions you'd like to enable:

typescript
// @declaration
// @filename: index.d.ts
declare global {
var process: NodeJS.Process;
namespace NodeJS {
interface Process {
env: Record<string, string>;
}
}
}
// @filename: ../../inngest/new_pr.ts
import { createFunction, InngestFunction } from "inngest";
export const newPR: InngestFunction = createFunction("New PR", "github/pull_request", ({ event }) => {
// This function is triggered when the `github/pull_request` event is received via
// a GH webhook.
});
// @filename: index.ts
// hack to get process.env to work
declare global {
var process: NodeJS.Process;
namespace NodeJS {
interface Process {
env: Record<string, string>;
}
}
}
// ---cut---
import { serve } from "inngest/next";
import { newPR } from "../../inngest/new_pr";
// You must export the serve handler, which hosts all of the provided functions
// under one API endpoint.
export default serve("My app/service name", [newPR]);

This hosts an API endpoint at ${your-url}/api/inngest, which we'll use to call your functions.

serve syntax

serve(appName, [functions]);
serve(appName, [functions], options);

serve arguments

  • appName: The name of your app or microservice, used to group all functions together
  • [functions]: An array of all functions to enable
  • options: An optional object of options, with the type:
ts
type Options = {
/**
* A key used to sign requests to and from Inngest in order to prove that the
* source is legitimate.
*
* You must provide a signing key to communicate securely with Inngest. If
* your key is not provided here, we'll try to retrieve it from the
* `INNGEST_SIGNING_KEY` environment variable.
*
* You can retrieve your signing key from the Inngest UI inside the "Secrets"
* section at {@link https://app.inngest.com/secrets}. We highly recommend
* that you add this to your platform's available environment variables as
* `INNGEST_SIGNING_KEY`.
*
* If no key can be found, you will not be able to register your functions or
* receive events from Inngest.
*/
signingKey?: string;
/**
* Controls whether a landing page with introspection capabilities is shown
* when a `GET` request is performed to this handler.
*
* Defaults to true if NODE_ENV !== production.
*
* This page is highly recommended when getting started in development,
* testing, or staging environments.
*/
landingPage?: boolean;
/**
* The URL used to register functions with Inngest.
* Defaults to https://api.inngest.com/fn/register
*/
inngestRegisterUrl?: string;
/**
* If provided, will override the used `fetch` implementation. Useful for
* giving the library a particular implementation if accessing it is not done
* via globals.
*
* By default the library will try to use the native Web API fetch, falling
* back to a Node implementation if no global fetch can be found.
*/
fetch?: typeof fetch;
}

Checking function configuration

To make sure everything is set up as you expect it to be, Inngest provides a local dashboard to introspect your served handler, show any found functions, and raise any errors before you get to production.

Visit ${your-url}/api/inngest (usually http://localhost:3000/api/inngest) to see the dashboard. If there are any problems detected, they'll show up here.

The SDK's local dashboard showing success

Deploying your Next.js application

When you deploy your application, Inngest needs to know where your applicaiton is running so it can call your functions. You can "register" your app in a couple of ways:

Registering automatically

Use one of our officially supported integrations which will automatically notify Inngest whenever you've deployed updated functions:

Registering manually

If you're deploying your appliction to another platform, you can register your app via the Inngest UI or via our API with a curl request.