Some checks failed
Test examples / Test Examples (20) (push) Has been cancelled
Test examples / Test Examples (22) (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Trigger Release / start (push) Has been cancelled
Stale issue handler / stale (push) Has been cancelled
Update Font Data / create-pull-request (push) Has been cancelled
build-and-deploy / deploy-target (push) Has been cancelled
build-and-deploy / build (push) Has been cancelled
build-and-deploy / stable - aarch64-unknown-linux-musl - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-unknown-linux-musl - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-unknown-linux-gnu - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-unknown-linux-gnu - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-pc-windows-msvc - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-pc-windows-msvc - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-apple-darwin - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-apple-darwin - node@16 (push) Has been cancelled
build-and-deploy / build-wasm (nodejs) (push) Has been cancelled
build-and-deploy / build-wasm (web) (push) Has been cancelled
build-and-deploy / Deploy preview tarball (push) Has been cancelled
build-and-deploy / Potentially publish release (push) Has been cancelled
build-and-deploy / publish-turbopack-npm-packages (push) Has been cancelled
build-and-deploy / Deploy examples (push) Has been cancelled
build-and-deploy / thank you, build (push) Has been cancelled
build-and-deploy / Upload Turbopack Bytesize metrics to Datadog (push) Has been cancelled
Rspack Next.js development integration tests / Rspack integration tests (push) Has been cancelled
Rspack Next.js production integration tests / Rspack integration tests (push) Has been cancelled
Turbopack Next.js development integration tests / Next.js integration tests (push) Has been cancelled
Turbopack Next.js production integration tests / Next.js integration tests (push) Has been cancelled
Update Rspack test manifest / Update and upload Rspack development test manifest (push) Has been cancelled
Update Rspack test manifest / Update and upload Rspack production test manifest (push) Has been cancelled
Upload bundler test manifests to areweturboyet.com / Upload test results (push) Has been cancelled
Update React / create-pull-request (push) Has been cancelled
test-e2e-project-reset-cron / reset-test-project (push) Has been cancelled
Notify about the top 15 issues/PRs/feature requests (most reacted) in the last 90 days / run (push) Has been cancelled
838 lines
28 KiB
Plaintext
838 lines
28 KiB
Plaintext
---
|
|
title: adapterPath
|
|
description: Configure a custom adapter for Next.js to hook into the build process.
|
|
version: experimental
|
|
---
|
|
|
|
Next.js provides a built-in adapters API. It allows deployment platforms or build systems to integrate with the Next.js build process.
|
|
|
|
For a full reference implementation, see the [`nextjs/adapter-vercel`](https://github.com/nextjs/adapter-vercel) adapter.
|
|
|
|
## Configuration
|
|
|
|
To use an adapter, specify the path to your adapter module in `experimental.adapterPath`:
|
|
|
|
```js filename="next.config.js"
|
|
/** @type {import('next').NextConfig} */
|
|
const nextConfig = {
|
|
experimental: {
|
|
adapterPath: require.resolve('./my-adapter.js'),
|
|
},
|
|
}
|
|
|
|
module.exports = nextConfig
|
|
```
|
|
|
|
Alternatively `NEXT_ADAPTER_PATH` can be set to enable zero-config usage in deployment platforms.
|
|
|
|
## Creating an Adapter
|
|
|
|
An adapter is a module that exports an object implementing the `NextAdapter` interface.
|
|
|
|
The interface can be imported from the `next` package:
|
|
|
|
```typescript
|
|
import type { NextAdapter } from 'next'
|
|
```
|
|
|
|
The interface is defined as follows:
|
|
|
|
```typescript
|
|
type Route = {
|
|
source?: string
|
|
sourceRegex: string
|
|
destination?: string
|
|
headers?: Record<string, string>
|
|
has?: RouteHas[]
|
|
missing?: RouteHas[]
|
|
status?: number
|
|
priority?: boolean
|
|
}
|
|
|
|
export interface AdapterOutputs {
|
|
pages: Array<AdapterOutput['PAGES']>
|
|
middleware?: AdapterOutput['MIDDLEWARE']
|
|
appPages: Array<AdapterOutput['APP_PAGE']>
|
|
pagesApi: Array<AdapterOutput['PAGES_API']>
|
|
appRoutes: Array<AdapterOutput['APP_ROUTE']>
|
|
prerenders: Array<AdapterOutput['PRERENDER']>
|
|
staticFiles: Array<AdapterOutput['STATIC_FILE']>
|
|
}
|
|
|
|
export interface NextAdapter {
|
|
name: string
|
|
modifyConfig?: (
|
|
config: NextConfigComplete,
|
|
ctx: {
|
|
phase: PHASE_TYPE
|
|
nextVersion: string
|
|
}
|
|
) => Promise<NextConfigComplete> | NextConfigComplete
|
|
onBuildComplete?: (ctx: {
|
|
routing: {
|
|
beforeMiddleware: Array<Route>
|
|
beforeFiles: Array<Route>
|
|
afterFiles: Array<Route>
|
|
dynamicRoutes: Array<Route>
|
|
onMatch: Array<Route>
|
|
fallback: Array<Route>
|
|
shouldNormalizeNextData: boolean
|
|
rsc: RoutesManifest['rsc']
|
|
}
|
|
outputs: AdapterOutputs
|
|
projectDir: string
|
|
repoRoot: string
|
|
distDir: string
|
|
config: NextConfigComplete
|
|
nextVersion: string
|
|
buildId: string
|
|
}) => Promise<void> | void
|
|
}
|
|
```
|
|
|
|
### Basic Adapter Structure
|
|
|
|
Here's a minimal adapter example:
|
|
|
|
```js filename="my-adapter.js"
|
|
/** @type {import('next').NextAdapter} */
|
|
const adapter = {
|
|
name: 'my-custom-adapter',
|
|
|
|
async modifyConfig(config, { phase }) {
|
|
// Modify the Next.js config based on the build phase
|
|
if (phase === 'phase-production-build') {
|
|
return {
|
|
...config,
|
|
// Add your modifications
|
|
}
|
|
}
|
|
return config
|
|
},
|
|
|
|
async onBuildComplete({
|
|
routing,
|
|
outputs,
|
|
projectDir,
|
|
repoRoot,
|
|
distDir,
|
|
config,
|
|
nextVersion,
|
|
buildId,
|
|
}) {
|
|
// Process the build output
|
|
console.log('Build completed with', outputs.pages.length, 'pages')
|
|
console.log('Build ID:', buildId)
|
|
console.log('Dynamic routes:', routing.dynamicRoutes.length)
|
|
|
|
// Access emitted output entries
|
|
for (const page of outputs.pages) {
|
|
console.log('Page:', page.pathname, 'at', page.filePath)
|
|
}
|
|
|
|
for (const apiRoute of outputs.pagesApi) {
|
|
console.log('API Route:', apiRoute.pathname, 'at', apiRoute.filePath)
|
|
}
|
|
|
|
for (const appPage of outputs.appPages) {
|
|
console.log('App Page:', appPage.pathname, 'at', appPage.filePath)
|
|
}
|
|
|
|
for (const prerender of outputs.prerenders) {
|
|
console.log('Prerendered:', prerender.pathname)
|
|
}
|
|
},
|
|
}
|
|
|
|
module.exports = adapter
|
|
```
|
|
|
|
## API Reference
|
|
|
|
### `async modifyConfig(config, context)`
|
|
|
|
Called for any CLI command that loads the `next.config.js` file to allow modification of the configuration.
|
|
|
|
**Parameters:**
|
|
|
|
- `config`: The complete Next.js configuration object
|
|
- `context.phase`: The current build phase (see [phases](/docs/app/api-reference/config/next-config-js#phase))
|
|
- `context.nextVersion`: Version of Next.js being used
|
|
|
|
**Returns:** The modified configuration object (can be async)
|
|
|
|
### `async onBuildComplete(context)`
|
|
|
|
Called after the build process completes with detailed information about routes and outputs.
|
|
|
|
**Parameters:**
|
|
|
|
- `context.routing`: Object containing Next.js routing phases and metadata
|
|
- `routing.beforeMiddleware`: Routes executed before middleware (includes header and redirect handling)
|
|
- `routing.beforeFiles`: Rewrite routes checked before filesystem route matching
|
|
- `routing.afterFiles`: Rewrite routes checked after filesystem route matching
|
|
- `routing.dynamicRoutes`: Dynamic route matching table
|
|
- `routing.onMatch`: Routes applied after a successful match (for example immutable static asset cache headers)
|
|
- `routing.fallback`: Final rewrite fallback routes
|
|
- `routing.shouldNormalizeNextData`: Whether `/_next/data/<buildId>/...` URLs should be normalized during matching
|
|
- `routing.rsc`: Route metadata used for React Server Components routing behavior
|
|
- `context.outputs`: Detailed information about all build outputs organized by type
|
|
- `context.projectDir`: Absolute path to the Next.js project directory
|
|
- `context.repoRoot`: Absolute path to the detected repository root
|
|
- `context.distDir`: Absolute path to the build output directory
|
|
- `context.config`: The final Next.js configuration (with `modifyConfig` applied)
|
|
- `context.nextVersion`: Version of Next.js being used
|
|
- `context.buildId`: Unique identifier for the current build
|
|
|
|
## Testing Adapters
|
|
|
|
Next.js provides a test harness for validating adapters. Running the end-to-end tests for deployment.
|
|
|
|
Example GitHub Actions workflow:
|
|
|
|
```yaml filename=".github/workflows/test-e2e-deploy.yml"
|
|
name: test-e2e-deploy
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
nextjsRef:
|
|
description: 'Next.js repo ref (branch/tag/SHA)'
|
|
default: 'canary'
|
|
type: string
|
|
# schedule:
|
|
# - cron: '0 2 * * *'
|
|
|
|
jobs:
|
|
build:
|
|
name: Build Next.js + adapter
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 30
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
path: adapter
|
|
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
repository: vercel/next.js
|
|
ref: ${{ inputs.nextjsRef || 'canary' }}
|
|
path: nextjs
|
|
fetch-depth: 25
|
|
|
|
- uses: actions/setup-node@v4
|
|
with: { node-version: '20' }
|
|
|
|
- name: Setup pnpm
|
|
run: npm i -g corepack@0.31 && corepack enable
|
|
|
|
- name: Install & build Next.js
|
|
working-directory: nextjs
|
|
run: pnpm install && pnpm build && pnpm install
|
|
|
|
- name: Install Playwright
|
|
working-directory: nextjs
|
|
run: pnpm playwright install --with-deps chromium
|
|
|
|
- name: Build adapter
|
|
working-directory: adapter
|
|
run: pnpm install && pnpm build
|
|
|
|
- uses: actions/cache/save@v4
|
|
with:
|
|
path: |
|
|
nextjs
|
|
adapter
|
|
~/.cache/ms-playwright
|
|
key: build-${{ github.sha }}-${{ github.run_id }}
|
|
|
|
test:
|
|
name: Tests (${{ matrix.group }})
|
|
needs: build
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 60
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
group:
|
|
[
|
|
1/16,
|
|
2/16,
|
|
3/16,
|
|
4/16,
|
|
5/16,
|
|
6/16,
|
|
7/16,
|
|
8/16,
|
|
9/16,
|
|
10/16,
|
|
11/16,
|
|
12/16,
|
|
13/16,
|
|
14/16,
|
|
15/16,
|
|
16/16,
|
|
]
|
|
steps:
|
|
- uses: actions/cache/restore@v4
|
|
with:
|
|
path: |
|
|
nextjs
|
|
adapter
|
|
~/.cache/ms-playwright
|
|
key: build-${{ github.sha }}-${{ github.run_id }}
|
|
|
|
- uses: actions/setup-node@v4
|
|
with: { node-version: '20' }
|
|
|
|
- name: Setup pnpm
|
|
run: npm i -g corepack@0.31 && corepack enable
|
|
|
|
- name: Ensure Playwright browser
|
|
working-directory: nextjs
|
|
run: pnpm playwright install chromium
|
|
|
|
- name: Make scripts executable
|
|
run: chmod +x adapter/scripts/e2e-deploy.sh
|
|
adapter/scripts/e2e-logs.sh
|
|
adapter/scripts/e2e-cleanup.sh
|
|
|
|
- name: Run deploy tests
|
|
working-directory: nextjs
|
|
env:
|
|
NEXT_TEST_MODE: deploy
|
|
NEXT_E2E_TEST_TIMEOUT: 240000
|
|
NEXT_EXTERNAL_TESTS_FILTERS: test/deploy-tests-manifest.json
|
|
ADAPTER_DIR: ${{ github.workspace }}/adapter
|
|
IS_TURBOPACK_TEST: 1
|
|
NEXT_TEST_JOB: 1
|
|
NEXT_TELEMETRY_DISABLED: 1
|
|
|
|
# Change these to your adapter's scripts
|
|
# Keep as-is if the scripts are in the adapter repository `scripts` directory
|
|
NEXT_TEST_DEPLOY_SCRIPT_PATH: ${{ github.workspace }}/adapter/scripts/e2e-deploy.sh
|
|
NEXT_TEST_DEPLOY_LOGS_SCRIPT_PATH: ${{ github.workspace }}/adapter/scripts/e2e-logs.sh
|
|
NEXT_TEST_CLEANUP_SCRIPT_PATH: ${{ github.workspace }}/adapter/scripts/e2e-cleanup.sh
|
|
run: node run-tests.js --timings -g ${{ matrix.group }} -c 2 --type e2e
|
|
```
|
|
|
|
The test harness looks for these environment variables:
|
|
|
|
- `NEXT_TEST_DEPLOY_SCRIPT_PATH`: Path to the executable that builds and deploys the isolated test app
|
|
- `NEXT_TEST_DEPLOY_LOGS_SCRIPT_PATH`: Path to the executable that returns build and runtime logs for that deployment
|
|
- `NEXT_TEST_CLEANUP_SCRIPT_PATH`: Path to the optional executable that tears the deployment down after the test run
|
|
|
|
### Custom deploy script contract
|
|
|
|
The deploy script `NEXT_TEST_DEPLOY_SCRIPT_PATH` is executed with `cwd` set to the isolated temporary app created by the Next.js test harness.
|
|
|
|
The deploy script must follow this contract:
|
|
|
|
- Exit with a non-zero code on failure.
|
|
- Print the deployment URL to `stdout`. This will be used to verify the deployment. Avoid writing anything else to `stdout`.
|
|
- Write diagnostic output to `stderr` or to files inside the working directory.
|
|
|
|
Because the deploy script and logs script run as separate processes, any data you want to use later, such as build IDs or server logs, should be persisted to files inside the working directory.
|
|
|
|
Example deploy script:
|
|
|
|
```bash filename="scripts/e2e-deploy.sh"
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
# Install the adapter, build the app, and deploy or start it.
|
|
node -e "
|
|
const pkg=JSON.parse(require('fs').readFileSync('package.json','utf8'));
|
|
pkg.dependencies=pkg.dependencies||{};
|
|
pkg.dependencies['adapter']='file:${ADAPTER_DIR}';
|
|
require('fs').writeFileSync('package.json',JSON.stringify(pkg,null,2));
|
|
" >&2
|
|
|
|
# Set the adapter path so that the app uses it.
|
|
export NEXT_ADAPTER_PATH="${ADAPTER_DIR}/dist/index.js"
|
|
|
|
# Write any metadata needed later to files in the working directory.
|
|
BUILD_ID="$(cat .next/BUILD_ID)"
|
|
DEPLOYMENT_ID="my-adapter-local"
|
|
# If your adapter generates an immutable asset token, set it here.
|
|
# Otherwise use "undefined" to indicate there is none.
|
|
IMMUTABLE_ASSET_TOKEN="undefined"
|
|
|
|
{
|
|
echo "BUILD_ID: $BUILD_ID"
|
|
echo "DEPLOYMENT_ID: $DEPLOYMENT_ID"
|
|
echo "IMMUTABLE_ASSET_TOKEN: $IMMUTABLE_ASSET_TOKEN"
|
|
} >> .adapter-build.log
|
|
|
|
# Build the app
|
|
pnpm build
|
|
|
|
# Start or deploy the app. Capture the URL at this point or make the script output the URL to stdout.
|
|
provider-cli-to-deploy
|
|
|
|
# Example URL output:
|
|
# echo "http://127.0.0.1:3000"
|
|
```
|
|
|
|
### Custom logs script contract
|
|
|
|
The logs script `NEXT_TEST_DEPLOY_LOGS_SCRIPT_PATH` is executed with `cwd` set to the isolated temporary app created by the Next.js test harness.
|
|
|
|
Additionally it receives `NEXT_TEST_DIR` and `NEXT_TEST_DEPLOY_URL` as environment variables.
|
|
|
|
Its output must include lines starting with:
|
|
|
|
- `BUILD_ID:`
|
|
- `DEPLOYMENT_ID:`
|
|
- `IMMUTABLE_ASSET_TOKEN:` (use the value `undefined` if your adapter does not produce one)
|
|
|
|
After those markers, the logs script can print any additional build or server logs that would help debug failures.
|
|
|
|
```bash filename="scripts/e2e-logs.sh"
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
if [ -f ".adapter-build.log" ]; then
|
|
cat ".adapter-build.log"
|
|
fi
|
|
|
|
if [ -f ".adapter-server.log" ]; then
|
|
echo "=== .adapter-server.log ==="
|
|
cat ".adapter-server.log"
|
|
fi
|
|
```
|
|
|
|
One pattern is to have the deploy script write `.adapter-build.log` and `.adapter-server.log`, then have the logs script replay those files so the harness can extract the required markers. This is one option, each platform has different ways to get the logs.
|
|
|
|
### Custom cleanup script contract
|
|
|
|
The cleanup script `NEXT_TEST_CLEANUP_SCRIPT_PATH` is executed with `cwd` set to the isolated temporary app created by the Next.js test harness.
|
|
|
|
Additionally it receives `NEXT_TEST_DIR` and `NEXT_TEST_DEPLOY_URL` as environment variables.
|
|
|
|
The cleanup script can be used to clean up any resources created by the deploy script. It runs after the tests have completed.
|
|
|
|
## Routing with `@next/routing`
|
|
|
|
You can use [`@next/routing`](https://www.npmjs.com/package/@next/routing) to reproduce Next.js route matching behavior with data from `onBuildComplete`.
|
|
|
|
> [!NOTE]
|
|
> `@next/routing` is experimental and will stabilize with the adapters API.
|
|
|
|
```typescript
|
|
import { resolveRoutes } from '@next/routing'
|
|
|
|
const pathnames = [
|
|
...outputs.pages,
|
|
...outputs.pagesApi,
|
|
...outputs.appPages,
|
|
...outputs.appRoutes,
|
|
...outputs.staticFiles,
|
|
].map((output) => output.pathname)
|
|
|
|
const result = await resolveRoutes({
|
|
url: requestUrl,
|
|
buildId,
|
|
basePath: config.basePath || '',
|
|
i18n: config.i18n,
|
|
headers: requestHeaders,
|
|
requestBody,
|
|
pathnames,
|
|
routes: routing,
|
|
invokeMiddleware: async (ctx) => {
|
|
// platform-specific middleware invocation
|
|
return {}
|
|
},
|
|
})
|
|
```
|
|
|
|
## Implementing PPR in an Adapter
|
|
|
|
For partially prerendered app routes, `onBuildComplete` gives you the data needed to seed and resume PPR:
|
|
|
|
- `outputs.prerenders[].fallback.filePath`: path to the generated fallback shell (for example HTML)
|
|
- `outputs.prerenders[].fallback.postponedState`: serialized postponed state used to resume rendering
|
|
|
|
### 1. Seed shell + postponed state at build time
|
|
|
|
```ts filename="my-adapter.ts"
|
|
import { readFile } from 'node:fs/promises'
|
|
|
|
async function seedPprEntries(outputs: AdapterOutputs) {
|
|
for (const prerender of outputs.prerenders) {
|
|
const fallback = prerender.fallback
|
|
if (!fallback?.filePath || !fallback.postponedState) continue
|
|
|
|
const shell = await readFile(fallback.filePath, 'utf8')
|
|
await platformCache.set(prerender.pathname, {
|
|
shell,
|
|
postponedState: fallback.postponedState,
|
|
initialHeaders: fallback.initialHeaders,
|
|
initialStatus: fallback.initialStatus,
|
|
initialRevalidate: fallback.initialRevalidate,
|
|
initialExpiration: fallback.initialExpiration,
|
|
})
|
|
}
|
|
}
|
|
```
|
|
|
|
### 2. Runtime flow: serve cached shell and resume in background
|
|
|
|
At request time, you can stream a single response that is the concatenation of:
|
|
|
|
1. cached HTML shell stream
|
|
2. resumed render stream (generated after invoking `handler` with postponed state)
|
|
|
|
```text
|
|
Client
|
|
| GET /ppr-route
|
|
v
|
|
Adapter Router
|
|
|
|
|
|-- read cached shell + postponedState ---> Platform Cache
|
|
|<------------- cache hit -----------------|
|
|
|
|
|
|-- create responseStream = concat(shellStream, resumedStream)
|
|
|
|
|
|-- start piping shellStream ------------> Client (first bytes)
|
|
|
|
|
|-- invoke handler(req, res, { requestMeta: { postponed } })
|
|
| -------------------------------------> Entrypoint (handler)
|
|
| <------------------------------------- resumed chunks/cache entry
|
|
|
|
|
|-- append resumed chunks to resumedStream
|
|
|
|
|
'-- client receives one HTTP response:
|
|
[shell bytes........][resumed bytes........]
|
|
```
|
|
|
|
### 3. Update cache with `requestMeta.onCacheEntryV2`
|
|
|
|
`requestMeta.onCacheEntryV2` is called when a response cache entry is looked up or generated. Use it to persist updated shell/postponed data.
|
|
|
|
> [!NOTE]
|
|
>
|
|
> - `requestMeta.onCacheEntry` still works, but is deprecated.
|
|
> - Prefer `requestMeta.onCacheEntryV2`.
|
|
> - If your adapter uses an internal `onCacheCallback` abstraction, wire it to `requestMeta.onCacheEntryV2`.
|
|
|
|
```ts filename="my-adapter.ts"
|
|
await handler(req, res, {
|
|
waitUntil,
|
|
requestMeta: {
|
|
postponed: cachedPprEntry?.postponedState,
|
|
onCacheEntryV2: async (cacheEntry, meta) => {
|
|
if (cacheEntry.value?.kind === 'APP_PAGE') {
|
|
const html =
|
|
cacheEntry.value.html &&
|
|
typeof cacheEntry.value.html.toUnchunkedString === 'function'
|
|
? cacheEntry.value.html.toUnchunkedString()
|
|
: null
|
|
|
|
await platformCache.set(meta.url || req.url || '/', {
|
|
shell: html,
|
|
postponedState: cacheEntry.value.postponed,
|
|
headers: cacheEntry.value.headers,
|
|
status: cacheEntry.value.status,
|
|
cacheControl: cacheEntry.cacheControl,
|
|
})
|
|
}
|
|
|
|
// Return true only if your adapter already wrote the response itself.
|
|
return false
|
|
},
|
|
},
|
|
})
|
|
```
|
|
|
|
```text
|
|
Entrypoint (handler)
|
|
| onCacheEntryV2(cacheEntry, { url })
|
|
v
|
|
requestMeta.onCacheEntryV2 callback
|
|
|
|
|
|-- if APP_PAGE ---> persist html + postponedState + headers ---> Platform Cache
|
|
|
|
|
'-- return false: continue normal Next.js response flow
|
|
return true: adapter already handled response (short-circuit)
|
|
```
|
|
|
|
## Invoking Entrypoints
|
|
|
|
Build output entrypoints use a `handler(..., ctx)` interface, with runtime-specific request/response types.
|
|
|
|
### Node.js runtime (`runtime: 'nodejs'`)
|
|
|
|
Node.js entrypoints use the following interface:
|
|
|
|
```typescript
|
|
handler(
|
|
req: IncomingMessage,
|
|
res: ServerResponse,
|
|
ctx: {
|
|
waitUntil?: (promise: Promise<void>) => void
|
|
requestMeta?: RequestMeta
|
|
}
|
|
): Promise<void>
|
|
```
|
|
|
|
Relevant files in the Next.js core:
|
|
|
|
- [`packages/next/src/build/templates/app-page.ts`](https://github.com/vercel/next.js/blob/canary/packages/next/src/build/templates/app-page.ts)
|
|
- [`packages/next/src/build/templates/app-route.ts`](https://github.com/vercel/next.js/blob/canary/packages/next/src/build/templates/app-route.ts)
|
|
- and [`packages/next/src/build/templates/pages-api.ts`](https://github.com/vercel/next.js/blob/canary/packages/next/src/build/templates/pages-api.ts)
|
|
|
|
### Edge runtime (`runtime: 'edge'`)
|
|
|
|
Edge entrypoints use the following interface:
|
|
|
|
```typescript
|
|
handler(
|
|
request: Request,
|
|
ctx: {
|
|
waitUntil?: (prom: Promise<void>) => void
|
|
signal?: AbortSignal
|
|
requestMeta?: RequestMeta
|
|
}
|
|
): Promise<Response>
|
|
```
|
|
|
|
The shape is aligned around `handler(..., ctx)`, but Node.js and Edge runtimes use different request/response primitives.
|
|
|
|
Relevant files in the Next.js core:
|
|
|
|
- [`packages/next/src/build/templates/edge-ssr.ts`](https://github.com/vercel/next.js/blob/canary/packages/next/src/build/templates/edge-ssr.ts)
|
|
- [`packages/next/src/build/templates/edge-app-route.ts`](https://github.com/vercel/next.js/blob/canary/packages/next/src/build/templates/edge-app-route.ts)
|
|
- [`packages/next/src/build/templates/pages-edge-api.ts`](https://github.com/vercel/next.js/blob/canary/packages/next/src/build/templates/pages-edge-api.ts)
|
|
- and [`packages/next/src/build/templates/middleware.ts`](https://github.com/vercel/next.js/blob/canary/packages/next/src/build/templates/middleware.ts)
|
|
|
|
## Output Types
|
|
|
|
The `outputs` object contains arrays of build output types:
|
|
|
|
- `outputs.pages`: React pages from the `pages/` directory
|
|
- `outputs.pagesApi`: API routes from `pages/api/`
|
|
- `outputs.appPages`: React pages from the `app/` directory
|
|
- `outputs.appRoutes`: API and metadata routes from `app/`
|
|
- `outputs.prerenders`: ISR-enabled routes and static prerenders
|
|
- `outputs.staticFiles`: Static assets and auto-statically optimized pages
|
|
- `outputs.middleware`: Middleware function (if present)
|
|
|
|
> **Note:** When `config.output` is set to `'export'`, only `outputs.staticFiles` is populated. All other arrays (`pages`, `appPages`, `pagesApi`, `appRoutes`, `prerenders`) will be empty since the entire application is exported as static files.
|
|
|
|
### Pages (`outputs.pages`)
|
|
|
|
React pages from the `pages/` directory:
|
|
|
|
```typescript
|
|
{
|
|
type: 'PAGES'
|
|
id: string // Route identifier
|
|
filePath: string // Path to the built file
|
|
pathname: string // URL pathname
|
|
sourcePage: string // Original source file path in pages/ directory
|
|
runtime: 'nodejs' | 'edge'
|
|
assets: Record<string, string> // Traced dependencies (key: relative path from repo root, value: absolute path)
|
|
wasmAssets?: Record<string, string> // Bundled wasm files (key: name, value: absolute path)
|
|
config: {
|
|
maxDuration?: number // Maximum duration of the route in seconds
|
|
preferredRegion?: string | string[] // Preferred deployment region
|
|
env?: Record<string, string> // Environment variables (edge runtime only)
|
|
}
|
|
}
|
|
```
|
|
|
|
### API Routes (`outputs.pagesApi`)
|
|
|
|
API routes from `pages/api/`:
|
|
|
|
```typescript
|
|
{
|
|
type: 'PAGES_API'
|
|
id: string // Route identifier
|
|
filePath: string // Path to the built file
|
|
pathname: string // URL pathname
|
|
sourcePage: string // Original relative source file path
|
|
runtime: 'nodejs' | 'edge'
|
|
assets: Record<string, string> // Traced dependencies (key: relative path from repo root, value: absolute path)
|
|
wasmAssets?: Record<string, string> // Bundled wasm files (key: name, value: absolute path)
|
|
config: {
|
|
maxDuration?: number // Maximum duration of the route in seconds
|
|
preferredRegion?: string | string[] // Preferred deployment region
|
|
env?: Record<string, string> // Environment variables (edge runtime only)
|
|
}
|
|
}
|
|
```
|
|
|
|
### App Pages (`outputs.appPages`)
|
|
|
|
React pages from the `app/` directory:
|
|
|
|
```typescript
|
|
{
|
|
type: 'APP_PAGE'
|
|
id: string // Route identifier
|
|
filePath: string // Path to the built file
|
|
pathname: string // URL pathname.Includes .rsc suffix for RSC routes
|
|
sourcePage: string // Original relative source file path
|
|
runtime: 'nodejs' | 'edge' // Runtime the route is built for
|
|
assets: Record<string, string> // Traced dependencies (key: relative path from repo root, value: absolute path)
|
|
wasmAssets?: Record<string, string> // Bundled wasm files (key: name, value: absolute path)
|
|
config: {
|
|
maxDuration?: number // Maximum duration of the route in seconds
|
|
preferredRegion?: string | string[] // Preferred deployment region
|
|
env?: Record<string, string> // Environment variables (edge runtime only)
|
|
}
|
|
}
|
|
```
|
|
|
|
### App Routes (`outputs.appRoutes`)
|
|
|
|
API and metadata routes from the `app/` directory:
|
|
|
|
```typescript
|
|
{
|
|
type: 'APP_ROUTE'
|
|
id: string // Route identifier
|
|
filePath: string // Path to the built file
|
|
pathname: string // URL pathname
|
|
sourcePage: string // Original relative source file path
|
|
runtime: 'nodejs' | 'edge' // Runtime the route is built for
|
|
assets: Record<string, string> // Traced dependencies (key: relative path from repo root, value: absolute path)
|
|
wasmAssets?: Record<string, string> // Bundled wasm files (key: name, value: absolute path)
|
|
config: {
|
|
maxDuration?: number // Maximum duration of the route in seconds
|
|
preferredRegion?: string | string[] // Preferred deployment region
|
|
env?: Record<string, string> // Environment variables (edge runtime only)
|
|
}
|
|
}
|
|
```
|
|
|
|
### Prerenders (`outputs.prerenders`)
|
|
|
|
ISR-enabled routes and static prerenders:
|
|
|
|
```typescript
|
|
{
|
|
type: 'PRERENDER'
|
|
id: string // Route identifier
|
|
pathname: string // URL pathname
|
|
parentOutputId: string // ID of the source page/route
|
|
groupId: number // Revalidation group identifier (prerenders with same groupId revalidate together)
|
|
pprChain?: {
|
|
headers: Record<string, string> // PPR chain headers (e.g., 'x-nextjs-resume': '1')
|
|
}
|
|
parentFallbackMode?: false | null | string // false: no additional paths (fallback: false), null: blocking render, string: path to HTML fallback
|
|
fallback?: {
|
|
filePath: string | undefined // Path to the fallback file (HTML, JSON, or RSC)
|
|
initialStatus?: number // Initial status code
|
|
initialHeaders?: Record<string, string | string[]> // Initial headers
|
|
initialExpiration?: number // Initial expiration time in seconds
|
|
initialRevalidate?: number | false // Initial revalidate time in seconds, or false for fully static
|
|
postponedState: string | undefined // Serialized PPR state used for resuming rendering
|
|
}
|
|
config: {
|
|
allowQuery?: string[] // Allowed query parameters considered for the cache key
|
|
allowHeader?: string[] // Allowed headers for ISR
|
|
bypassFor?: RouteHas[] // Cache bypass conditions
|
|
renderingMode?: 'STATIC' | 'PARTIALLY_STATIC' // STATIC: fully static, PARTIALLY_STATIC: PPR-enabled
|
|
partialFallback?: boolean // Serves a partial fallback shell that should be upgraded to a full route in the background
|
|
bypassToken?: string // Generated token that signals the prerender cache should be bypassed
|
|
}
|
|
}
|
|
```
|
|
|
|
### Static Files (`outputs.staticFiles`)
|
|
|
|
Static assets and auto-statically optimized pages:
|
|
|
|
```typescript
|
|
{
|
|
type: 'STATIC_FILE'
|
|
id: string // Route identifier
|
|
filePath: string // Path to the built file
|
|
pathname: string // URL pathname
|
|
immutableHash: string | undefined // Content hash when the filename contains a hash, indicating the file is immutable
|
|
}
|
|
```
|
|
|
|
### Middleware (`outputs.middleware`)
|
|
|
|
`middleware.ts` (`.js`/`.ts`) or `proxy.ts` (`.js`/`.ts`) function (if present):
|
|
|
|
```typescript
|
|
{
|
|
type: 'MIDDLEWARE'
|
|
id: string // Route identifier
|
|
filePath: string // Path to the built file
|
|
pathname: string // Always '/_middleware'
|
|
sourcePage: string // Always 'middleware'
|
|
runtime: 'nodejs' | 'edge' // Runtime the route is built for
|
|
assets: Record<string, string> // Traced dependencies (key: relative path from repo root, value: absolute path)
|
|
wasmAssets?: Record<string, string> // Bundled wasm files (key: name, value: absolute path)
|
|
config: {
|
|
maxDuration?: number // Maximum duration of the route in seconds
|
|
preferredRegion?: string | string[] // Preferred deployment region
|
|
env?: Record<string, string> // Environment variables (edge runtime only)
|
|
matchers?: Array<{
|
|
source: string // Source pattern
|
|
sourceRegex: string // Compiled regex for matching requests
|
|
has: RouteHas[] | undefined // Positive matching conditions
|
|
missing: RouteHas[] | undefined // Negative matching conditions
|
|
}>
|
|
}
|
|
}
|
|
```
|
|
|
|
## Routing Information
|
|
|
|
The `routing` object in `onBuildComplete` provides complete routing information with processed patterns ready for deployment:
|
|
|
|
### `routing.beforeMiddleware`
|
|
|
|
Routes applied before middleware execution. These include generated header and redirect behavior.
|
|
|
|
### `routing.beforeFiles`
|
|
|
|
Rewrite routes checked before filesystem route matching.
|
|
|
|
### `routing.afterFiles`
|
|
|
|
Rewrite routes checked after filesystem route matching.
|
|
|
|
### `routing.dynamicRoutes`
|
|
|
|
Dynamic matchers generated from route segments such as `[slug]` and catch-all routes.
|
|
|
|
### `routing.onMatch`
|
|
|
|
Routes that apply after a successful match, such as immutable cache headers for hashed static assets.
|
|
|
|
### `routing.fallback`
|
|
|
|
Final rewrite routes checked when earlier phases did not produce a match.
|
|
|
|
### Common Route Fields
|
|
|
|
Each route entry can include:
|
|
|
|
- `source`: Original route pattern (optional for generated internal rules)
|
|
- `sourceRegex`: Compiled regex for matching requests
|
|
- `destination`: Internal destination or redirect destination
|
|
- `headers`: Headers to apply
|
|
- `has`: Positive matching conditions
|
|
- `missing`: Negative matching conditions
|
|
- `status`: Redirect status code
|
|
- `priority`: Internal route priority flag
|
|
|
|
## Use Cases
|
|
|
|
Common use cases for adapters include:
|
|
|
|
- **Deployment Platform Integration**: Automatically configure build outputs for specific hosting platforms
|
|
- **Asset Processing**: Transform or optimize build outputs
|
|
- **Monitoring Integration**: Collect build metrics and route information
|
|
- **Custom Bundling**: Package outputs in platform-specific formats
|
|
- **Build Validation**: Ensure outputs meet specific requirements
|
|
- **Route Generation**: Use processed route information to generate platform-specific routing configs
|