Skip to content

Commit f0ead09

Browse files
authored
Fix initialRevalidateSeconds manifest field with i18n (#17926)
This makes sure the correct `initialRevalidateSeconds` field is populated in the `prerender-manifest` for non-dynamic SSG pages since they will be inserted into the `initialPageRevalidationMap` under their locale prefixed variant with `i18n` enabled x-ref: #17370
1 parent 75f75f3 commit f0ead09

File tree

3 files changed

+127
-8
lines changed

3 files changed

+127
-8
lines changed

packages/next/build/index.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -959,21 +959,27 @@ export default async function build(
959959
}
960960

961961
if (isSsg) {
962+
const { i18n } = config.experimental
963+
962964
// For a non-dynamic SSG page, we must copy its data file from export.
963965
if (!isDynamic) {
964966
await moveExportedPage(page, page, file, true, 'json')
965967

968+
const revalidationMapPath = i18n
969+
? `/${i18n.defaultLocale}${page}`
970+
: page
971+
966972
finalPrerenderRoutes[page] = {
967973
initialRevalidateSeconds:
968-
exportConfig.initialPageRevalidationMap[page],
974+
exportConfig.initialPageRevalidationMap[revalidationMapPath],
969975
srcRoute: null,
970976
dataRoute: path.posix.join('/_next/data', buildId, `${file}.json`),
971977
}
972978
// Set Page Revalidation Interval
973979
const pageInfo = pageInfos.get(page)
974980
if (pageInfo) {
975981
pageInfo.initialRevalidateSeconds =
976-
exportConfig.initialPageRevalidationMap[page]
982+
exportConfig.initialPageRevalidationMap[revalidationMapPath]
977983
pageInfos.set(page, pageInfo)
978984
}
979985
} else {

packages/next/build/webpack/loaders/next-serverless-loader.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,8 @@ const nextServerlessLoader: loader.Loader = function () {
248248
let localeDomainRedirect
249249
const localePathResult = normalizeLocalePath(parsedUrl.pathname, i18n.locales)
250250
251+
routeNoAssetPath = normalizeLocalePath(routeNoAssetPath, i18n.locales).pathname
252+
251253
if (localePathResult.detectedLocale) {
252254
detectedLocale = localePathResult.detectedLocale
253255
req.url = formatUrl({
@@ -337,6 +339,7 @@ const nextServerlessLoader: loader.Loader = function () {
337339
res.end()
338340
return
339341
}
342+
340343
detectedLocale = detectedLocale || defaultLocale
341344
`
342345
: `
@@ -547,10 +550,10 @@ const nextServerlessLoader: loader.Loader = function () {
547550
548551
if (parsedUrl.pathname.match(/_next\\/data/)) {
549552
const {
550-
default: getrouteNoAssetPath,
553+
default: getRouteNoAssetPath,
551554
} = require('next/dist/next-server/lib/router/utils/get-route-from-asset-path');
552555
_nextData = true;
553-
parsedUrl.pathname = getrouteNoAssetPath(
556+
parsedUrl.pathname = getRouteNoAssetPath(
554557
parsedUrl.pathname.replace(
555558
new RegExp('/_next/data/${escapedBuildId}/'),
556559
'/'
@@ -609,12 +612,24 @@ const nextServerlessLoader: loader.Loader = function () {
609612
? `const nowParams = req.headers && req.headers["x-now-route-matches"]
610613
? getRouteMatcher(
611614
(function() {
612-
const { re, groups } = getRouteRegex("${page}");
615+
const { re, groups, routeKeys } = getRouteRegex("${page}");
613616
return {
614617
re: {
615618
// Simulate a RegExp match from the \`req.url\` input
616619
exec: str => {
617620
const obj = parseQs(str);
621+
622+
// favor named matches if available
623+
const routeKeyNames = Object.keys(routeKeys)
624+
625+
if (routeKeyNames.every(name => obj[name])) {
626+
return routeKeyNames.reduce((prev, keyName) => {
627+
const paramName = routeKeys[keyName]
628+
prev[groups[paramName].pos] = obj[keyName]
629+
return prev
630+
}, {})
631+
}
632+
618633
return Object.keys(obj).reduce(
619634
(prev, key) =>
620635
Object.assign(prev, {

test/integration/i18n-support/test/index.test.js

Lines changed: 101 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import fs from 'fs-extra'
55
import cheerio from 'cheerio'
66
import { join } from 'path'
77
import webdriver from 'next-webdriver'
8+
import escapeRegex from 'escape-string-regexp'
89
import {
910
fetchViaHTTP,
1011
findPort,
@@ -15,6 +16,7 @@ import {
1516
renderViaHTTP,
1617
File,
1718
waitFor,
19+
normalizeRegEx,
1820
} from 'next-test-utils'
1921

2022
jest.setTimeout(1000 * 60 * 2)
@@ -24,7 +26,7 @@ const nextConfig = new File(join(appDir, 'next.config.js'))
2426
let app
2527
let appPort
2628
let buildPagesDir
27-
// let buildId
29+
let buildId
2830

2931
const locales = ['en-US', 'nl-NL', 'nl-BE', 'nl', 'fr-BE', 'fr', 'en']
3032

@@ -59,6 +61,102 @@ function runTests(isDev) {
5961
],
6062
})
6163
})
64+
65+
it('should output correct prerender-manifest', async () => {
66+
const prerenderManifest = await fs.readJSON(
67+
join(appDir, '.next/prerender-manifest.json')
68+
)
69+
70+
for (const key of Object.keys(prerenderManifest.dynamicRoutes)) {
71+
const item = prerenderManifest.dynamicRoutes[key]
72+
item.routeRegex = normalizeRegEx(item.routeRegex)
73+
item.dataRouteRegex = normalizeRegEx(item.dataRouteRegex)
74+
}
75+
76+
expect(prerenderManifest.routes).toEqual({
77+
'/en-US/gsp/fallback/first': {
78+
dataRoute: `/_next/data/${buildId}/en-US/gsp/fallback/first.json`,
79+
initialRevalidateSeconds: false,
80+
srcRoute: '/gsp/fallback/[slug]',
81+
},
82+
'/en-US/gsp/fallback/second': {
83+
dataRoute: `/_next/data/${buildId}/en-US/gsp/fallback/second.json`,
84+
initialRevalidateSeconds: false,
85+
srcRoute: '/gsp/fallback/[slug]',
86+
},
87+
'/en-US/gsp/no-fallback/first': {
88+
dataRoute: `/_next/data/${buildId}/en-US/gsp/no-fallback/first.json`,
89+
initialRevalidateSeconds: false,
90+
srcRoute: '/gsp/no-fallback/[slug]',
91+
},
92+
'/en-US/gsp/no-fallback/second': {
93+
dataRoute: `/_next/data/${buildId}/en-US/gsp/no-fallback/second.json`,
94+
initialRevalidateSeconds: false,
95+
srcRoute: '/gsp/no-fallback/[slug]',
96+
},
97+
'/en-US/not-found/fallback/first': {
98+
dataRoute: `/_next/data/${buildId}/en-US/not-found/fallback/first.json`,
99+
initialRevalidateSeconds: false,
100+
srcRoute: '/not-found/fallback/[slug]',
101+
},
102+
'/en-US/not-found/fallback/second': {
103+
dataRoute: `/_next/data/${buildId}/en-US/not-found/fallback/second.json`,
104+
initialRevalidateSeconds: false,
105+
srcRoute: '/not-found/fallback/[slug]',
106+
},
107+
'/gsp': {
108+
dataRoute: `/_next/data/${buildId}/gsp.json`,
109+
srcRoute: null,
110+
initialRevalidateSeconds: false,
111+
},
112+
'/nl-NL/gsp/no-fallback/second': {
113+
dataRoute: `/_next/data/${buildId}/nl-NL/gsp/no-fallback/second.json`,
114+
initialRevalidateSeconds: false,
115+
srcRoute: '/gsp/no-fallback/[slug]',
116+
},
117+
'/not-found': {
118+
dataRoute: `/_next/data/${buildId}/not-found.json`,
119+
srcRoute: null,
120+
initialRevalidateSeconds: false,
121+
},
122+
})
123+
expect(prerenderManifest.dynamicRoutes).toEqual({
124+
'/gsp/fallback/[slug]': {
125+
routeRegex: normalizeRegEx(
126+
'^\\/gsp\\/fallback\\/([^\\/]+?)(?:\\/)?$'
127+
),
128+
dataRoute: `/_next/data/${buildId}/gsp/fallback/[slug].json`,
129+
fallback: '/gsp/fallback/[slug].html',
130+
dataRouteRegex: normalizeRegEx(
131+
`^\\/_next\\/data\\/${escapeRegex(
132+
buildId
133+
)}\\/gsp\\/fallback\\/([^\\/]+?)\\.json$`
134+
),
135+
},
136+
'/gsp/no-fallback/[slug]': {
137+
routeRegex: normalizeRegEx(
138+
'^\\/gsp\\/no\\-fallback\\/([^\\/]+?)(?:\\/)?$'
139+
),
140+
dataRoute: `/_next/data/${buildId}/gsp/no-fallback/[slug].json`,
141+
fallback: false,
142+
dataRouteRegex: normalizeRegEx(
143+
`^/_next/data/${escapeRegex(
144+
buildId
145+
)}/gsp/no\\-fallback/([^/]+?)\\.json$`
146+
),
147+
},
148+
'/not-found/fallback/[slug]': {
149+
dataRoute: `/_next/data/${buildId}/not-found/fallback/[slug].json`,
150+
dataRouteRegex: normalizeRegEx(
151+
`^\\/_next\\/data\\/${escapeRegex(
152+
buildId
153+
)}\\/not\\-found\\/fallback\\/([^\\/]+?)\\.json$`
154+
),
155+
fallback: '/not-found/fallback/[slug].html',
156+
routeRegex: normalizeRegEx('^/not\\-found/fallback/([^/]+?)(?:/)?$'),
157+
},
158+
})
159+
})
62160
}
63161

64162
it('should navigate with locale prop correctly', async () => {
@@ -1145,7 +1243,7 @@ describe('i18n Support', () => {
11451243
appPort = await findPort()
11461244
app = await nextStart(appDir, appPort)
11471245
buildPagesDir = join(appDir, '.next/server/pages')
1148-
// buildId = await fs.readFile(join(appDir, '.next/BUILD_ID'), 'utf8')
1246+
buildId = await fs.readFile(join(appDir, '.next/BUILD_ID'), 'utf8')
11491247
})
11501248
afterAll(() => killApp(app))
11511249

@@ -1161,7 +1259,7 @@ describe('i18n Support', () => {
11611259
appPort = await findPort()
11621260
app = await nextStart(appDir, appPort)
11631261
buildPagesDir = join(appDir, '.next/serverless/pages')
1164-
// buildId = await fs.readFile(join(appDir, '.next/BUILD_ID'), 'utf8')
1262+
buildId = await fs.readFile(join(appDir, '.next/BUILD_ID'), 'utf8')
11651263
})
11661264
afterAll(async () => {
11671265
nextConfig.restore()

0 commit comments

Comments
 (0)