Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Called after the build process completes with detailed information about routes
**Parameters:**

- `context.routing`: Object containing Next.js routing phases and metadata
- `routing.version`: Version of the routing contract. Version `2` keeps dynamic Pages API routes in the canonical `/api` namespace. Manifests without a version use the legacy version `1` semantics.
- `routing.beforeMiddleware`: Routes executed before middleware (includes header and redirect handling)
- `routing.middlewareMatchers`: Middleware matcher definitions for this build, used to decide whether middleware should be invoked for a given request
- `routing.beforeFiles`: Rewrite routes checked before filesystem route matching
Expand Down
9 changes: 8 additions & 1 deletion packages/next/src/build/adapter/build-complete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { interopDefault } from '../../lib/interop-default'
import type { RouteHas } from '../../lib/load-custom-routes'
import { recursiveReadDir } from '../../lib/recursive-readdir'
import { isDynamicRoute } from '../../shared/lib/router/utils'
import { isAPIRoute } from '../../lib/is-api-route'
import type { Revalidate } from '../../server/lib/cache-control'
import type { NextConfigComplete } from '../../server/config-shared'
import {
Expand Down Expand Up @@ -520,6 +521,11 @@ export interface NextAdapter {
) => Promise<NextConfigComplete> | NextConfigComplete
onBuildComplete?: (ctx: {
routing: {
/**
* Version of the routing manifest contract. Unversioned manifests use
* the legacy version 1 semantics.
*/
version: 2
beforeMiddleware: Array<Route>
/**
* middlewareMatchers are the middleware matcher definitions emitted by
Expand Down Expand Up @@ -2125,7 +2131,7 @@ export async function handleBuildComplete({
route.page
)

const shouldLocalize = Boolean(config.i18n)
const shouldLocalize = Boolean(config.i18n && !isAPIRoute(route.page))

const routeRegex = getNamedRouteRegex(route.page, {
prefixRouteKeys: true,
Expand Down Expand Up @@ -2423,6 +2429,7 @@ export async function handleBuildComplete({

await adapterMod.onBuildComplete({
routing: {
version: 2,
beforeMiddleware: [...headers, ...redirects],
middlewareMatchers:
outputs.middleware?.config.matchers?.map((matcher) => ({
Expand Down
11 changes: 4 additions & 7 deletions test/production/adapter-config-i18n/adapter-config-i18n.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,7 @@ describe('adapter config with i18n routes', () => {
files: __dirname,
})

// Dynamic Pages API routes are localized like any other dynamic route. The
// Vercel adapter's i18n rules prefix the locale onto `/api/...` too, so the
// matcher has to expect it — if the two disagree the matcher is unreachable
// and requests fall through to `/{locale}/404`. See vercel/next.js#96935.
it('localizes dynamic Pages API routes', async () => {
it('keeps dynamic Pages API routes in the canonical namespace', async () => {
const { outputs, routing }: Parameters<NextAdapter['onBuildComplete']>[0] =
await next.readJSON('build-complete.json')

Expand All @@ -24,11 +20,12 @@ describe('adapter config with i18n routes', () => {
(route) => route.source === '/blog/[slug]'
)

expect(routing.version).toBe(2)
expect(apiOutput).toBeDefined()
expect(apiRoute).toBeDefined()
expect(apiRoute?.sourceRegex).toContain('nextLocale')
expect(apiRoute?.sourceRegex).not.toContain('nextLocale')
expect(apiRoute?.destination).toBe(
'/$nextLocale/api/proxy/[[...slug]]?nxtPslug=$nxtPslug'
'/api/proxy/[[...slug]]?nxtPslug=$nxtPslug'
)

expect(pageRoute).toBeDefined()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ describe('adapter-config export', () => {
}

expect(routing).toEqual({
version: 2,
beforeMiddleware: expect.toBeArray(),
middlewareMatchers: expect.toBeArray(),
beforeFiles: expect.toBeArray(),
Expand Down
1 change: 1 addition & 0 deletions test/production/adapter-config/adapter-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,7 @@ describe('adapter-config', () => {
}

expect(routing).toEqual({
version: 2,
beforeMiddleware: expect.toBeArray(),
middlewareMatchers: expect.toBeArray(),
beforeFiles: expect.toBeArray(),
Expand Down
Loading