-
Notifications
You must be signed in to change notification settings - Fork 9
Next.js Integration Guide #316
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
bd5e6ad
Next MG
chuck-dbos 841ba1c
Move to 'integrations' for consistency
chuck-dbos f2e6807
Copy sections over
chuck-dbos de03faa
A few more sections
chuck-dbos 038d084
Touchups
chuck-dbos d6e3f2c
Box off and change font
chuck-dbos eb6a5e2
Merge remote-tracking branch 'origin/main' into chuck/nextguide
chuck-dbos 733a7d2
Add whole server.ts
chuck-dbos 9fa654a
Soften / simplify wording
chuck-dbos 193cf6e
Exta word
chuck-dbos 3895cb7
Shorten
chuck-dbos 9837928
Attempt to clarify a bit
chuck-dbos bc6c8f9
Make sections prescriptive
chuck-dbos 76b8a11
Typo
chuck-dbos a9b2613
Merge remote-tracking branch 'origin/main' into chuck/nextguide
chuck-dbos 09422ac
Fix debugger link
chuck-dbos File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,287 @@ | ||
--- | ||
sidebar_position: 40 | ||
title: Adding DBOS to Next.js Apps | ||
description: Learn how to integrate DBOS into Next.js applications. | ||
--- | ||
|
||
# Introduction | ||
|
||
## Why Add DBOS To a Next.js Application? | ||
[Next.js](https://nextjs.org/) is a solid choice for high-performance interactive web frontends. By adding DBOS Transact, and running in a suitable hosting environment (such as [DBOS Cloud](https://www.dbos.dev/dbos-cloud)), your Next.js application can implement "heavy lifting" tasks with: | ||
- Lightweight durable execution – DBOS [workflows](../typescript/tutorials/workflow-tutorial) run to completion exactly once. | ||
- Simple, powerful database integration – [Manage database data](../typescript/tutorials/transaction-tutorial) with DBOS. | ||
- Cron-style task scheduling – Automate recurring jobs with [cron-like scheduling](../typescript/tutorials/scheduled-workflows). | ||
- Built-in tracing and replay debugging – [Find workflows in the dashboard](../cloud-tutorials/monitoring-dashboard) and [re-run them locally](../cloud-tutorials/interactive-timetravel). | ||
|
||
## Architectural Overview | ||
Next.js is a framework that optimizes where and when [React](https://react.dev/) UI components render—whether on the client, server, or edge—while also handling routing, data fetching, and performance optimizations. Part of this architecture involves the creation of minimized code bundles for handling requests. These bundles can be loaded quickly in a “serverless” environment, leading to minimal request latency even when the server is “cold”. This “serverless” style of deployment precludes any long-running jobs, background tasks that execute while no client requests are pending, or long-lived server objects such as WebSockets: | ||
 | ||
|
||
However, Next.js also supports “custom server” deployments, which do not have limitations on long-running tasks and long-lived state. The custom server loads code when launched, and the optimized request handler bundles can then interact with this server-side code: | ||
 | ||
|
||
The latter architecture is a requirement for using Next.js with DBOS. This guide covers switching to a deployment with a custom `server.ts` file, and creating, calling, building, and deploying the logic within it. | ||
|
||
## Integration Overview | ||
To add DBOS to a Next.JS application: | ||
- Use your package manager to install `@dbos-inc/dbos-sdk` into your project. | ||
- Add DBOS startup code to the custom server file, often named `server.ts` or similar. (If a custom server is not in use in your project, it will be necessary to add one. This may also affect how Next.js is launched, and additional build steps may be required.) | ||
- Add files containing workflow logic implemented with DBOS. Ensure that the files are being compiled, and load the code from `server.ts` at startup. | ||
- Add calls to the DBOS code from Next.js code. This can include server-side page renders, server actions, and route handlers. | ||
- Ensure that your workflow functions and the DBOS library code do not get bundled into the bundles used by Next.js to handle the requests. (Failure to do this will lead to a wide variety of error messages.) This guide covers two techniques: configuring `webpack` to treat the files and modules as external, or using `globalThis` to get a reference to the shared objects and code set up in `server.ts`. | ||
- Run and troubleshoot the development environment and production build. | ||
|
||
# Adding DBOS To Next.JS Applications | ||
|
||
## Coding `server.ts` | ||
`server.ts` (note that, while any file name can be used, `server.ts` is the common convention that is used in this guide) is responsible for initializing DBOS, and launching Next.js. | ||
|
||
### Adding DBOS Code To an Existing `server.ts` | ||
If you already have a `server.ts` or similar file, add the following code to it: | ||
```typescript | ||
// ... Other library imports ... | ||
// highlight-next-line | ||
import { DBOS } from '@dbos-inc/dbos-sdk'; // Import DBOS | ||
|
||
// | ||
// ... In subsequent steps, we will also import any app code that uses DBOS. | ||
// This will provide the code and other objects needed for DBOS to recover any workflows... | ||
|
||
async function main() { // Adjust if your entrypoint is not named 'main' | ||
// ... Configure anything DBOS needs prior to launch ... | ||
|
||
// Launch DBOS after initializing DBOS logic, and before starting 'next' | ||
// highlight-next-line | ||
await DBOS.launch(); | ||
|
||
// ... Then proceed to start the Next.js server ... | ||
} | ||
``` | ||
|
||
### Adding `server.ts` To a Project | ||
If your project does not currently have a `server.ts` file or similar, or if you are using the default `server.js` provided by Next.js, the first step is to create one. This file can be created in the top level of your project, inside `src/`, or in any sensible place within your app structure. | ||
|
||
```typescript | ||
import next from 'next'; | ||
import http, { IncomingMessage, ServerResponse } from 'http'; | ||
|
||
// highlight-next-line | ||
import { DBOS } from '@dbos-inc/dbos-sdk'; | ||
// highlight-next-line | ||
// imports of DBOS workflows and other code will go here... | ||
|
||
const dev = process.env.NODE_ENV !== 'production'; | ||
const app = next({ dev }); | ||
const handle = app.getRequestHandler(); | ||
|
||
async function main() { | ||
DBOS.logger.info('Launching...'); | ||
// highlight-next-line | ||
await DBOS.launch(); | ||
DBOS.logger.info(' ...launched.'); | ||
|
||
DBOS.logger.info(`Doing Next App Prepare...`); | ||
await app.prepare(); | ||
DBOS.logger.info(` ...prepared.`); | ||
|
||
// Create HTTP server | ||
const server = http.createServer((req, res) => { | ||
handle(req, res as ServerResponse<IncomingMessage>); | ||
}); | ||
|
||
const PORT = DBOS.runtimeConfig?.port ?? 3000; | ||
const ENV = process.env.NODE_ENV || 'development'; | ||
|
||
server.listen(PORT, () => { | ||
DBOS.logger.info(`🚀 Server is running on http://localhost:${PORT}`); | ||
DBOS.logger.info(`🌟 Environment: ${ENV}`); | ||
}); | ||
} | ||
|
||
// Only start the server when this file is run directly from Node | ||
if (require.main === module) { | ||
main().catch((error) => { | ||
console.error('❌ Server failed to start:', error); | ||
DBOS.logger.error('❌ Server failed to start:', error); | ||
process.exit(1); // Ensure the process exits on failure | ||
}); | ||
} | ||
``` | ||
|
||
The DBOS demo apps contain example `server.ts` files to copy: | ||
- [DBOS Next.js Template](https://github.com/dbos-inc/dbos-demo-apps/blob/main/typescript/dbos-nextjs-starter/src/server.ts): This project uses a basic `server.ts` file. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would include a simple copy-pasteable There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. K |
||
- [DBOS Task Scheduler](https://github.com/dbos-inc/dbos-demo-apps/blob/main/typescript/nextjs-calendar/src/server.ts): This project uses a more sophisticated `server.ts` file with WebSocket support, etc. | ||
|
||
### Compilation Settings For DBOS Code | ||
DBOS and `server.ts` require some TypeScript compiler settings. These can either be added to your existing `tsconfig.json`, or if they are incompatible with the existing `tsconfig.json` settings, a separate `tsconfig.server.json` file can be created for use with the server builds. | ||
|
||
```json | ||
{ | ||
"compilerOptions": { | ||
"experimentalDecorators": true, | ||
"emitDecoratorMetadata": true, | ||
"noEmit": false, | ||
"outDir": "./dist", | ||
}, | ||
"exclude": [ | ||
"node_modules", | ||
"dist" | ||
] | ||
} | ||
``` | ||
|
||
Noteworthy settings: | ||
- `experimentalDecorators`: Used by the DBOS decorator framework | ||
- `emitDecoratorMetadata`: Used by the DBOS decorator framework | ||
- `noEmit`, `outDir`, and `exclude`: Many Next.js projects do not emit the `.js` files corresponding to the `.ts` files; the bundles hold all the code. With a custom `server.ts`, `.js` files are needed for server code for execution by the `node` runtime. | ||
|
||
### `package.json` Scripts | ||
Scripts such as `build`, `dev`, and `start` may require modification to work with `server.ts`. Commands such as `next dev` and `next start` will not load `server.ts`; Next.js should be started via the `server.js` file produced by compiling your `server.ts` file. | ||
|
||
The following shows very basic scripts for `package.json`: | ||
```json | ||
"scripts": { | ||
"dev": "npx dbos migrate && node dist/src/server.js", | ||
"build": "tsc && next build", | ||
"start": "NODE_ENV=production node dist/src/server.js", | ||
}, | ||
``` | ||
|
||
These scripts can be more sophisticated, allowing for database migrations and `nodemon`. See the [demo apps](https://github.com/dbos-inc/dbos-demo-apps/tree/main/typescript) for examples. | ||
|
||
### DBOS Configuration | ||
By default, `DBOS.launch()` will read [`dbos-config.yaml`](../typescript/reference/configuration) to get key information, such as database settings. To configure DBOS programatically, see [`DBOS.setConfig`](../typescript/reference/transactapi/dbos-class#setting-the-application-configuration). | ||
|
||
## Calling DBOS Code From Next.js | ||
The DBOS Transact library and your workflow functions are available in all server-side request handling, including page loads, server actions, and route handlers. | ||
|
||
The following example shows a server action that starts a workflow: | ||
```typescript | ||
"use server"; | ||
|
||
import { DBOS } from "@dbos-inc/dbos-sdk"; | ||
import { MyWorkflow } from "@dbos/operations"; | ||
|
||
// This action uses DBOS to idempotently launch a crashproof background task with N steps. | ||
export async function startBackgroundTask(taskID: string, steps: number) { | ||
await DBOS.startWorkflow(MyWorkflow, { workflowID: taskID }).backgroundTask(steps); | ||
return "Background task started!"; | ||
} | ||
``` | ||
|
||
This example executes DBOS code from within a route: | ||
```typescript | ||
import { NextResponse } from 'next/server'; | ||
|
||
export async function GET() { | ||
// Note: DBOSBored.getActivity() is a DBOS workflow function | ||
const dbb = await globalThis.DBOSBored!.getActivity(); | ||
return NextResponse.json(dbb); | ||
} | ||
``` | ||
|
||
## Preventing Bundling | ||
As explained in the [Architectural Overview](#architectural-overview) above, the DBOS library, workflow functions, and related code must remain external to Next.js bundles. If DBOS code gets bundled, the bundles will contain incomplete functions, object instances, queue definitions, etc., many of which will be duplicated across bundles. These bundles will be loaded at runtime in response to Next.js requests, leading to a confusing mess of DBOS registration errors. | ||
|
||
The Next.js bundling process traces `import`ed dependencies from pages, actions, and API routes and then minimizes them. To prevent DBOS code from being bundled, applications can take either or both of the following approaches, as desired: | ||
- Mark Modules as External – Configure `webpack` to treat DBOS modules and files as external, ensuring they are not pulled into the bundles. | ||
- Use `globalThis` – Accessing code and data through [`globalThis`](https://ja-visser.medium.com/globalreferences-in-nodejs-75f095962596) instead of `import` effectively bypasses the bundler. | ||
|
||
### Configuring `webpack` to Treat `import`s as External | ||
`webpack` should be configured to treat the following as external dependencies: | ||
- The DBOS Transact library: `@dbos-inc/dbos-sdk` | ||
- Any DBOS [package libraries](../typescript/reference/libraries.md), such as `@dbos-inc/dbos-ses` | ||
- Any application source files implementing steps, transactions, or workflows | ||
- Any application source files that create or accesses DBOS [queues](../typescript/reference/transactapi/workflow-queues.md), [object instances](../typescript/tutorials/instantiated-objects), or other DBOS objects | ||
|
||
Ensure that each such module or file is covered in `next.config.ts`, within `webpack`'s `config.externals` array. Entries in `config.externals` must match the names used in `import` statements for recognition to work correctly. It is not necessary to list each external file individually, as the `config.externals` array can also accept regular expressions and callback functions. For example, if an application `import`s all DBOS logic with the module alias `@dbos/` (such as `import { MyWorkflow } from "@dbos/operations"`), only a single regular expression of `/^@dbos\/.+$/` is needed to treat all files as external: | ||
|
||
```typescript | ||
webpack: (config, { isServer, dev: _dev }) => { | ||
if (isServer) { | ||
config.externals = [ | ||
...config.externals, | ||
{ | ||
// highlight-next-line | ||
"@dbos-inc/dbos-sdk": "commonjs @dbos-inc/dbos-sdk", // Treat the @dbos-inc/dbos-sdk module as an external | ||
}, | ||
// highlight-next-line | ||
/^@dbos\/.+$/, // Treat ALL `@dbos/*` imports (from src/dbos) as external | ||
]; | ||
} | ||
|
||
return config; | ||
}, | ||
``` | ||
|
||
Note that for aliases such as `@dbos/` to work, two additional components are required. Paths must be set up in `tsconfig.json` so that `tsc` resolves the imports at compile time, and [`module-alias`](https://www.npmjs.com/package/module-alias) or similar should be used so that the alias is resolved by `node` at runtime. | ||
|
||
### Using `globalThis` | ||
Instead of using `import` statements, code for Next.js actions and routes can use `globalThis` to access data and code that has been set up by `server.ts`. (Unscoped variables will not work, as each bundle will have its own set of such "global" variables; [`globalThis`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/globalThis) should be used.) | ||
|
||
#### Placing Items In `globalThis` | ||
Items may be placed in `globalThis` directly. This should generally be done before DBOS and Next.js are started. | ||
|
||
```typescript | ||
// Share a non-DBOS object globally | ||
globalThis.webSocketClients = gss; | ||
|
||
// Share a DBOS configured object globally | ||
globalThis.reportSes = DBOS.configureInstance(DBOS_SES, 'reportSES', {awscfgname: 'aws_config'}); | ||
|
||
// Share an entire class globally | ||
import { DBOSBored } from "./dbos_bored"; | ||
globalThis.DBOSBored = DBOSBored; | ||
``` | ||
|
||
#### Retrieving Items From `globalThis` | ||
Items may be read from `globalThis` from within DBOS code and Next.js server-side renders, server actions, and route handlers. Items stored in the server's `globalThis` are not available on the client. | ||
|
||
```typescript | ||
const gss = globalThis.webSocketClients; | ||
await globalThis.reportSes.sendEmail({/*...*/}); | ||
await globalThis.DBOSBored!.getActivity(); | ||
``` | ||
|
||
#### Applying TypeScript Types To `globalThis` | ||
By default, `globalThis` has the `any` type, which is not ideal for type safety. While `globalThis` can be cast to an interface at each place it is used, another solution is to place a `global.d.ts` in your project, and list any globals there. | ||
|
||
```typescript | ||
/* eslint-disable no-var */ | ||
import WebSocket from "ws"; | ||
import { DBOS_SES } from "@dbos-inc/dbos-email-ses"; | ||
import { DBOSBored as DBOSBoredT } from "@dbos/operations"; | ||
|
||
export declare global { | ||
var webSocketClients: Set<WebSocket> | undefined; | ||
var reportSes: DBOS_SES | undefined; | ||
var DBOSBored: typeof DBOSBoredT | undefined; | ||
}; | ||
``` | ||
|
||
## Running and Troubleshooting | ||
In case of any issues, the following troubleshooting steps are suggested. Start by diagnosing `dev`, and then proceed to production. | ||
- Check and see if compilation is succeeding. Check `tsc`, `next build`, and any other steps individually. | ||
- Ensure that the `server.js` file and others are is available for execution by `node`. If not, check `tsconfig.json` settings, and look for any compiler errors from `tsc`. | ||
- Check for errors in the startup of `node` via the `dev` or `start` target in `package.json`. Ensure that database credentials are available, that migrations have been run, and that the `.js` files are where they should be. | ||
- If server startup is not working, look for any signs that module resolution or other settings are not correct in `tsconfig.json`. | ||
- If the server is starting but is hitting an error during initialization, ensure that there are adequate logging statements in the `main` (or similar) function. This will narrow down whether any problems are with DBOS, Next.js, or something else. | ||
- If DBOS is reporting errors prior to `launch()`, the issue is likely with incomplete code in `server.ts`, or a registration error in the decorators of the DBOS functions. | ||
- If DBOS errors indicating duplicate or missing registrations are occurring during request processing, the cause is almost always with DBOS code getting bundled. Use `next build` with `analyze` mode, or another approach, to ensure that this is not happening. Also ensure that all code is available and loaded (directly or indirectly) from `server.ts` prior to `DBOS.launch()`, which in turn should precede Next.js startup. | ||
|
||
### Other `next.config.ts` Options | ||
For Next.js server actions to work, it may be necessary to configure allowed origins in `next.config.ts`. For example, to allow server actions to work in DBOS Cloud, the following was added: | ||
```typescript | ||
experimental: { | ||
serverActions: { | ||
allowedOrigins: ['*.cloud.dbos.dev'], // Allow DBOS Cloud to call server actions | ||
}, | ||
}, | ||
``` | ||
|
||
# Next Steps | ||
- If your DBOS code is accessing the application database, check out the [transactions tutorial](../typescript/tutorials/transaction-tutorial) and consider database setup using [schema migration](../typescript/programming-guide#5-database-operations-and-transactions). | ||
- Review the [programming guide](../typescript/programming-guide) for information on job scheduling, queues, and other DBOS features. | ||
- Check out DBOS [programming examples](../examples) | ||
- Launch your Next.js app to [DBOS Cloud](../cloud-tutorials/application-management). | ||
|
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.