Skip to content

adapterPath

Last updated March 20, 2026

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 adapter.

Configuration

To use an adapter, specify the path to your adapter module in adapterPath:

next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  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:

import type { NextAdapter } from 'next'

The interface is defined as follows:

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:

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)
  • 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:

.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:

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.

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 to reproduce Next.js route matching behavior with data from onBuildComplete.

@next/routing is experimental and will stabilize with the adapters API.

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 {}
  },
})
 
if (result.resolvedPathname) {
  console.log('Resolved pathname:', result.resolvedPathname)
  console.log('Resolved query:', result.resolvedQuery)
  console.log('Invocation target:', result.invocationTarget)
}

resolveRoutes() returns:

  • resolvedPathname: The route pathname selected by Next.js routing. For dynamic routes, this is the matched route template such as /blog/[slug].
  • resolvedQuery: The final query after rewrites or middleware have added or replaced search params.
  • invocationTarget: The concrete pathname and query to invoke for the matched route.

For example, if /blog/post-1?draft=1 matches /blog/[slug]?slug=post-1, resolvedPathname is /blog/[slug] while invocationTarget.pathname is /blog/post-1.

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

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)
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.

  • requestMeta.onCacheEntry still works, but is deprecated.
  • Prefer requestMeta.onCacheEntryV2.
  • If your adapter uses an internal onCacheCallback abstraction, wire it to requestMeta.onCacheEntryV2.
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
    },
  },
})
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:

handler(
  req: IncomingMessage,
  res: ServerResponse,
  ctx: {
    waitUntil?: (promise: Promise<void>) => void
    requestMeta?: RequestMeta
  }
): Promise<void>

When invoking Node.js entrypoints directly, adapters can pass helpers directly on requestMeta instead of relying on internals. Some of the supported fields are hostname, revalidate, and render404:

await handler(req, res, {
  requestMeta: {
    // Relative path from process.cwd() to the Next.js project directory.
    relativeProjectDir: '.',
    // Optional hostname used by route handlers when constructing absolute URLs.
    hostname: '127.0.0.1',
    // Optional internal revalidate function to avoid revalidating over the network
    revalidate: async ({ urlPath, headers, opts }) => {
      // platform-specific revalidate implementation
    },
    // Optional function to render the 404 page for pages router `notFound: true`
    render404: async (req, res, parsedUrl, setHeaders) => {
      // platform-specific 404 rendering implementation
    },
  },
})

Relevant files in the Next.js core:

Edge runtime (runtime: 'edge')

Edge entrypoints use the following interface:

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.

For outputs with runtime: 'edge', Next.js also provides output.edgeRuntime with the canonical metadata needed to invoke the entrypoint:

{
  modulePath: string // Absolute path to the module registered in the edge runtime
  entryKey: string // Canonical key used by the edge entry registry
  handlerExport: string // Export name to invoke, currently 'handler'
}

After your edge runtime loads and evaluates the chunks for modulePath, use entryKey to read the registered entry from the global edge entry registry (globalThis._ENTRIES), then invoke handlerExport from that entry:

const entry = await globalThis._ENTRIES[output.edgeRuntime.entryKey]
const handler = entry[output.edgeRuntime.handlerExport]
await handler(request, ctx)

Use edgeRuntime instead of deriving registry keys or handler names from filenames.

Relevant files in the Next.js core:

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.

For any route output with runtime: 'edge', edgeRuntime is included and contains the canonical entry metadata for invoking that output in your edge runtime.

Pages (outputs.pages)

React pages from the pages/ directory:

{
  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)
  edgeRuntime?: {
    modulePath: string    // Absolute path to the module registered in the edge runtime
    entryKey: string      // Canonical key used by the edge entry registry
    handlerExport: string // Export name to invoke, currently 'handler'
  }
  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/:

{
  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)
  edgeRuntime?: {
    modulePath: string    // Absolute path to the module registered in the edge runtime
    entryKey: string      // Canonical key used by the edge entry registry
    handlerExport: string // Export name to invoke, currently 'handler'
  }
  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:

{
  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)
  edgeRuntime?: {
    modulePath: string    // Absolute path to the module registered in the edge runtime
    entryKey: string      // Canonical key used by the edge entry registry
    handlerExport: string // Export name to invoke, currently 'handler'
  }
  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:

{
  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)
  edgeRuntime?: {
    modulePath: string    // Absolute path to the module registered in the edge runtime
    entryKey: string      // Canonical key used by the edge entry registry
    handlerExport: string // Export name to invoke, currently 'handler'
  }
  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:

{
  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:

{
  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):

{
  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)
  edgeRuntime?: {
    modulePath: string    // Absolute path to the module registered in the edge runtime
    entryKey: string      // Canonical key used by the edge entry registry
    handlerExport: string // Export name to invoke, currently 'handler'
  }
  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

Was this helpful?

supported.