Skip to content
Merged
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
3 changes: 2 additions & 1 deletion packages/next/errors.json
Original file line number Diff line number Diff line change
Expand Up @@ -986,5 +986,6 @@
"985": "No response is returned from route handler '%s'. Expected a Response object but received '%s' (method: %s, url: %s). Ensure you return a \\`Response\\` or a \\`NextResponse\\` in all branches of your handler.",
"986": "Server Action arguments list is too long (%s). Maximum allowed is %s.",
"987": "Invalid \\`deploymentId\\` configuration: must be a string. See https://nextjs.org/docs/messages/deploymentid-not-a-string",
"988": "Invalid \\`deploymentId\\` configuration: contains invalid characters. Only alphanumeric characters, hyphens, and underscores are allowed. See https://nextjs.org/docs/messages/deploymentid-invalid-characters"
"988": "Invalid \\`deploymentId\\` configuration: contains invalid characters. Only alphanumeric characters, hyphens, and underscores are allowed. See https://nextjs.org/docs/messages/deploymentid-invalid-characters",
"989": "Loaded static props were from an outdated deployment, forcing a hard reload"
}
45 changes: 24 additions & 21 deletions packages/next/src/build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2791,7 +2791,7 @@ export default async function build(
...serverPropsPages,
...ssgPages,
]).map((page) => {
return buildDataRoute(page, buildId)
return buildDataRoute(page, buildId, config.deploymentId)
})
}

Expand Down Expand Up @@ -3876,11 +3876,13 @@ export default async function build(
experimentalPPR: undefined,
renderingMode: undefined,
srcRoute: null,
dataRoute: path.posix.join(
'/_next/data',
buildId,
`${localePage}.json`
),
dataRoute: config.deploymentId
? path.posix.join('/_next/data', `${localePage}.json`)
: path.posix.join(
'/_next/data',
buildId,
`${localePage}.json`
),
prefetchDataRoute: undefined,
allowHeader: ALLOWED_HEADERS,
}
Expand All @@ -3894,11 +3896,9 @@ export default async function build(
experimentalPPR: undefined,
renderingMode: undefined,
srcRoute: null,
dataRoute: path.posix.join(
'/_next/data',
buildId,
`${file}.json`
),
dataRoute: config.deploymentId
? path.posix.join('/_next/data', `${file}.json`)
: path.posix.join('/_next/data', buildId, `${file}.json`),
// Pages does not have a prefetch data route.
prefetchDataRoute: undefined,
allowHeader: ALLOWED_HEADERS,
Expand Down Expand Up @@ -3939,11 +3939,16 @@ export default async function build(
experimentalPPR: undefined,
renderingMode: undefined,
srcRoute: page,
dataRoute: path.posix.join(
'/_next/data',
buildId,
`${normalizePagePath(route.pathname)}.json`
),
dataRoute: config.deploymentId
? path.posix.join(
'/_next/data',
`${normalizePagePath(route.pathname)}.json`
)
: path.posix.join(
'/_next/data',
buildId,
`${normalizePagePath(route.pathname)}.json`
),
// Pages does not have a prefetch data route.
prefetchDataRoute: undefined,
allowHeader: ALLOWED_HEADERS,
Expand Down Expand Up @@ -4052,11 +4057,9 @@ export default async function build(
if (ssgPages.size > 0 || appDir) {
tbdPrerenderRoutes.forEach((tbdRoute) => {
const normalizedRoute = normalizePagePath(tbdRoute)
const dataRoute = path.posix.join(
'/_next/data',
buildId,
`${normalizedRoute}.json`
)
const dataRoute = config.deploymentId
? path.posix.join('/_next/data', `${normalizedRoute}.json`)
: path.posix.join('/_next/data', buildId, `${normalizedRoute}.json`)

prerenderManifest.dynamicRoutes[tbdRoute] = {
routeRegex: normalizeRouteRegex(
Expand Down
7 changes: 6 additions & 1 deletion packages/next/src/client/page-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
DEV_CLIENT_MIDDLEWARE_MANIFEST,
} from '../shared/lib/constants'
import { resolvePromiseWithTimeout } from './lib/promise'
import { getDeploymentId } from '../shared/lib/deployment-id'

declare global {
interface Window {
Expand Down Expand Up @@ -170,8 +171,12 @@ export default class PageLoader {
removeTrailingSlash(addLocale(path, locale)),
'.json'
)

// We check the response header instead
return addBasePath(
`/_next/data/${this.buildId}${dataRoute}${search}`,
getDeploymentId()
? `/_next/data${dataRoute}${search}`
: `/_next/data/${this.buildId}${dataRoute}${search}`,
true
)
}
Expand Down
2 changes: 2 additions & 0 deletions packages/next/src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ export const NEXT_DATA_SUFFIX = '.json'
export const NEXT_META_SUFFIX = '.meta'
export const NEXT_BODY_SUFFIX = '.body'

export const NEXT_NAV_DEPLOYMENT_ID_HEADER = 'x-nextjs-deployment-id'

export const NEXT_CACHE_TAGS_HEADER = 'x-next-cache-tags'
export const NEXT_CACHE_REVALIDATED_TAGS_HEADER = 'x-next-revalidated-tags'
export const NEXT_CACHE_REVALIDATE_TAG_TOKEN_HEADER =
Expand Down
21 changes: 17 additions & 4 deletions packages/next/src/lib/load-custom-routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { escapeStringRegexp } from '../shared/lib/escape-regexp'
import { tryToParsePath } from './try-to-parse-path'
import { allowedStatusCodes } from './redirect-status'
import { isFullStringUrl } from './url'
import { NEXT_NAV_DEPLOYMENT_ID_HEADER } from './constants'

export type RouteHas =
| {
Expand Down Expand Up @@ -746,13 +747,25 @@ export default async function loadCustomRoutes(
)
}

if (config.experimental?.useSkewCookie && config.deploymentId) {
if (config.deploymentId) {
if (config.experimental?.useSkewCookie) {
headers.unshift({
source: '/:path*',
headers: [
{
key: 'Set-Cookie',
value: `__vdpl=${config.deploymentId}; Path=/; HttpOnly`,
},
],
})
}

headers.unshift({
source: '/:path*',
source: '/_next/data/(.*)',
headers: [
{
key: 'Set-Cookie',
value: `__vdpl=${config.deploymentId}; Path=/; HttpOnly`,
key: NEXT_NAV_DEPLOYMENT_ID_HEADER,
value: config.deploymentId,
},
],
})
Expand Down
35 changes: 18 additions & 17 deletions packages/next/src/server/base-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -524,7 +524,7 @@ export default abstract class Server<
? new SegmentPrefixRSCPathnameNormalizer()
: undefined,
data: this.enabledDirectories.pages
? new NextDataPathnameNormalizer(this.buildId)
? new NextDataPathnameNormalizer(this.buildId, this.deploymentId)
: undefined,
}

Expand Down Expand Up @@ -680,20 +680,22 @@ export default abstract class Server<
return false
}

if (params.path[0] !== this.buildId) {
// Ignore if its a middleware request when we aren't on edge.
if (getRequestMeta(req, 'middlewareInvoke')) {
return false
if (!this.nextConfig.deploymentId) {
if (params.path[0] !== this.buildId) {
// Ignore if its a middleware request when we aren't on edge.
if (getRequestMeta(req, 'middlewareInvoke')) {
return false
}

// Make sure to 404 if the buildId isn't correct
await this.render404(req, res, parsedUrl)
return true
}

// Make sure to 404 if the buildId isn't correct
await this.render404(req, res, parsedUrl)
return true
// remove buildId from URL
params.path.shift()
}

// remove buildId from URL
params.path.shift()

const lastParam = params.path[params.path.length - 1]

// show 404 if it doesn't end with .json
Expand Down Expand Up @@ -2440,13 +2442,12 @@ export default abstract class Server<
}

private stripNextDataPath(filePath: string, stripLocale = true) {
if (filePath.includes(this.buildId)) {
const splitPath = filePath.substring(
filePath.indexOf(this.buildId) + this.buildId.length
)

filePath = denormalizePagePath(splitPath.replace(/\.json$/, ''))
if (this.deploymentId) {
filePath = filePath.replace(/\/_next\/data/, '')
} else {
filePath = filePath.replace(/\/_next\/data\/[^/]+/, '')
}
filePath = filePath.replace(/\.json$/, '')

if (this.localeNormalizer && stripLocale) {
return this.localeNormalizer.normalize(filePath)
Expand Down
74 changes: 52 additions & 22 deletions packages/next/src/server/lib/router-utils/build-data-route.test.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,59 @@
import { buildDataRoute } from './build-data-route'

describe('buildDataRoute', () => {
it('should build a dynamic data route', () => {
const dataRoute = buildDataRoute('/[...slug]', '123')
expect(dataRoute).toMatchInlineSnapshot(`
{
"dataRouteRegex": "^/_next/data/123/(.+?)\\.json$",
"namedDataRouteRegex": "^/_next/data/123/(?<nxtPslug>.+?)\\.json$",
"page": "/[...slug]",
"routeKeys": {
"nxtPslug": "nxtPslug",
},
}
`)
describe('with build id', () => {
it('should build a dynamic data route', () => {
const dataRoute = buildDataRoute('/[...slug]', '123', '')
expect(dataRoute).toMatchInlineSnapshot(`
{
"dataRouteRegex": "^/_next/data/123/(.+?)\\.json$",
"namedDataRouteRegex": "^/_next/data/123/(?<nxtPslug>.+?)\\.json$",
"page": "/[...slug]",
"routeKeys": {
"nxtPslug": "nxtPslug",
},
}
`)
})

it('should build a static data route', () => {
const dataRoute = buildDataRoute('/about', '123', '')
expect(dataRoute).toMatchInlineSnapshot(`
{
"dataRouteRegex": "^/_next/data/123/about\\.json$",
"namedDataRouteRegex": undefined,
"page": "/about",
"routeKeys": undefined,
}
`)
})
})

it('should build a static data route', () => {
const dataRoute = buildDataRoute('/about', '123')
expect(dataRoute).toMatchInlineSnapshot(`
{
"dataRouteRegex": "^/_next/data/123/about\\.json$",
"namedDataRouteRegex": undefined,
"page": "/about",
"routeKeys": undefined,
}
`)
describe('with deployment id', () => {
it('should build a dynamic data route', () => {
const dataRoute = buildDataRoute('/[...slug]', '123', 'ABCDEF')
expect(dataRoute).toMatchInlineSnapshot(`
{
"dataRouteRegex": "^/_next/data/(.+?)\\.json$",
"namedDataRouteRegex": "^/_next/data/(?<nxtPslug>.+?)\\.json$",
"page": "/[...slug]",
"routeKeys": {
"nxtPslug": "nxtPslug",
},
}
`)
})

it('should build a static data route', () => {
const dataRoute = buildDataRoute('/about', '123', 'ABCDEF')
expect(dataRoute).toMatchInlineSnapshot(`
{
"dataRouteRegex": "^/_next/data/about\\.json$",
"namedDataRouteRegex": undefined,
"page": "/about",
"routeKeys": undefined,
}
`)
})
})
})
24 changes: 17 additions & 7 deletions packages/next/src/server/lib/router-utils/build-data-route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,15 @@ import { getNamedRouteRegex } from '../../../shared/lib/router/utils/route-regex
import { normalizeRouteRegex } from '../../../lib/load-custom-routes'
import { escapeStringRegexp } from '../../../shared/lib/escape-regexp'

export function buildDataRoute(page: string, buildId: string) {
export function buildDataRoute(
page: string,
buildId: string,
deploymentId: string
) {
const pagePath = normalizePagePath(page)
const dataRoute = path.posix.join('/_next/data', buildId, `${pagePath}.json`)
const dataRoute = deploymentId
? path.posix.join('/_next/data', `${pagePath}.json`)
: path.posix.join('/_next/data', buildId, `${pagePath}.json`)

let dataRouteRegex: string
let namedDataRouteRegex: string | undefined
Expand All @@ -26,11 +32,15 @@ export function buildDataRoute(page: string, buildId: string) {
} else {
dataRouteRegex = normalizeRouteRegex(
new RegExp(
`^${path.posix.join(
'/_next/data',
escapeStringRegexp(buildId),
`${pagePath}\\.json`
)}$`
`^${
deploymentId
? path.posix.join('/_next/data', `${pagePath}\\.json`)
: path.posix.join(
'/_next/data',
escapeStringRegexp(buildId),
`${pagePath}\\.json`
)
}$`
).source
)
}
Expand Down
23 changes: 17 additions & 6 deletions packages/next/src/server/lib/router-utils/filesystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -283,10 +283,16 @@ export async function setupFsCheck(opts: {
// upstream builder that relies on this
re: opts.config.i18n
? new RegExp(
route.dataRouteRegex.replace(
`/${escapedBuildId}/`,
`/${escapedBuildId}/(?<nextLocale>[^/]+?)/`
)
opts.config.deploymentId ||
opts.config.experimental.runtimeServerDeploymentId
? route.dataRouteRegex.replace(
`/_next/data/`,
`/_next/data/(?<nextLocale>[^/]+?)/`
)
: route.dataRouteRegex.replace(
`/_next/data/${escapedBuildId}/`,
`/_next/data/${escapedBuildId}/(?<nextLocale>[^/]+?)/`
)
)
: new RegExp(route.dataRouteRegex),
groups: routeRegex.groups,
Expand Down Expand Up @@ -574,15 +580,20 @@ export async function setupFsCheck(opts: {
continue
}

const nextDataPrefix = `/_next/data/${buildId}/`
// When deploymentId is set, data URLs don't include the build ID
const nextDataPrefix =
opts.config.deploymentId ||
opts.config.experimental.runtimeServerDeploymentId
? '/_next/data/'
: `/_next/data/${buildId}/`

if (
type === 'pageFile' &&
curItemPath.startsWith(nextDataPrefix) &&
curItemPath.endsWith('.json')
) {
items = nextDataRoutes
// remove _next/data/<build-id> prefix
// remove _next/data/ or _next/data/<build-id>/ prefix
curItemPath = curItemPath.substring(nextDataPrefix.length - 1)

// remove .json postfix
Expand Down
5 changes: 4 additions & 1 deletion packages/next/src/server/lib/router-utils/resolve-routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,10 @@ export function getResolveRoutes(
config.basePath && config.basePath !== '/'
? new BasePathPathnameNormalizer(config.basePath)
: undefined,
data: new NextDataPathnameNormalizer(fsChecker.buildId),
data: new NextDataPathnameNormalizer(
fsChecker.buildId,
config.deploymentId || ''
),
}

async function handleRoute(
Expand Down
Loading
Loading