Add support for rewriting non-fallback SSG pages (#11010)
Since non-fallback pages don't rely on the URL for hydration we can allow them to be rewritten to but pages with fallback still can't be rewritten to because we won't be able to parse the correct `/_next/data` path to request the page's data from. I added a test for this behavior and ensured it works correctly on Now. Example on with fallback false rewrite on Now: https://tst-rewrite-cp9vge4bg.now.sh/about
This commit is contained in:
parent
6a993d5972
commit
ae3c388039
4 changed files with 110 additions and 10 deletions
|
@ -580,6 +580,7 @@ export default class Server {
|
|||
}
|
||||
}
|
||||
;(req as any)._nextDidRewrite = true
|
||||
;(req as any)._nextRewroteUrl = newUrl
|
||||
|
||||
return {
|
||||
finished: false,
|
||||
|
@ -970,8 +971,12 @@ export default class Server {
|
|||
isPreviewMode = previewData !== false
|
||||
}
|
||||
|
||||
// Compute the iSSG cache key
|
||||
let urlPathname = `${parseUrl(req.url || '').pathname!}`
|
||||
// Compute the iSSG cache key. We use the rewroteUrl since
|
||||
// pages with fallback: false are allowed to be rewritten to
|
||||
// and we need to look up the path by the rewritten path
|
||||
let urlPathname = (req as any)._nextRewroteUrl
|
||||
? (req as any)._nextRewroteUrl
|
||||
: `${parseUrl(req.url || '').pathname!}`
|
||||
|
||||
// remove /_next/data prefix from urlPathname so it matches
|
||||
// for direct page visit and /_next/data visit
|
||||
|
|
12
test/integration/prerender/next.config.js
Normal file
12
test/integration/prerender/next.config.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
module.exports = {
|
||||
experimental: {
|
||||
rewrites() {
|
||||
return [
|
||||
{
|
||||
source: '/about',
|
||||
destination: '/lang/en/about',
|
||||
},
|
||||
]
|
||||
},
|
||||
},
|
||||
}
|
12
test/integration/prerender/pages/lang/[lang]/about.js
Normal file
12
test/integration/prerender/pages/lang/[lang]/about.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
export default ({ lang }) => <p>About: {lang}</p>
|
||||
|
||||
export const getStaticProps = ({ params: { lang } }) => ({
|
||||
props: {
|
||||
lang,
|
||||
},
|
||||
})
|
||||
|
||||
export const getStaticPaths = () => ({
|
||||
paths: ['en', 'es', 'fr', 'de'].map((p) => `/lang/${p}/about`),
|
||||
fallback: false,
|
||||
})
|
|
@ -36,6 +36,7 @@ let buildId
|
|||
let distPagesDir
|
||||
let exportDir
|
||||
let stderr
|
||||
let origConfig
|
||||
|
||||
const startServer = async (optEnv = {}) => {
|
||||
const scriptPath = join(appDir, 'server.js')
|
||||
|
@ -130,6 +131,26 @@ const expectedManifestRoutes = () => ({
|
|||
initialRevalidateSeconds: false,
|
||||
srcRoute: null,
|
||||
},
|
||||
'/lang/de/about': {
|
||||
dataRoute: `/_next/data/${buildId}/lang/de/about.json`,
|
||||
initialRevalidateSeconds: false,
|
||||
srcRoute: '/lang/[lang]/about',
|
||||
},
|
||||
'/lang/en/about': {
|
||||
dataRoute: `/_next/data/${buildId}/lang/en/about.json`,
|
||||
initialRevalidateSeconds: false,
|
||||
srcRoute: '/lang/[lang]/about',
|
||||
},
|
||||
'/lang/es/about': {
|
||||
dataRoute: `/_next/data/${buildId}/lang/es/about.json`,
|
||||
initialRevalidateSeconds: false,
|
||||
srcRoute: '/lang/[lang]/about',
|
||||
},
|
||||
'/lang/fr/about': {
|
||||
dataRoute: `/_next/data/${buildId}/lang/fr/about.json`,
|
||||
initialRevalidateSeconds: false,
|
||||
srcRoute: '/lang/[lang]/about',
|
||||
},
|
||||
'/something': {
|
||||
dataRoute: `/_next/data/${buildId}/something.json`,
|
||||
initialRevalidateSeconds: false,
|
||||
|
@ -492,6 +513,11 @@ const runTests = (dev = false, looseMode = false) => {
|
|||
const html = await res.text()
|
||||
expect(html).toMatch(/This page could not be found/)
|
||||
})
|
||||
|
||||
it('should allow rewriting to SSG page with fallback: false', async () => {
|
||||
const html = await renderViaHTTP(appPort, '/about')
|
||||
expect(html).toMatch(/About:.*?en/)
|
||||
})
|
||||
}
|
||||
|
||||
if (dev) {
|
||||
|
@ -828,6 +854,18 @@ const runTests = (dev = false, looseMode = false) => {
|
|||
),
|
||||
page: '/default-revalidate',
|
||||
},
|
||||
{
|
||||
namedDataRouteRegex: `^/_next/data/${escapeRegex(
|
||||
buildId
|
||||
)}/lang/(?<lang>[^/]+?)/about\\.json$`,
|
||||
dataRouteRegex: normalizeRegEx(
|
||||
`^\\/_next\\/data\\/${escapeRegex(
|
||||
buildId
|
||||
)}\\/lang\\/([^\\/]+?)\\/about\\.json$`
|
||||
),
|
||||
page: '/lang/[lang]/about',
|
||||
routeKeys: ['lang'],
|
||||
},
|
||||
{
|
||||
namedDataRouteRegex: `^/_next/data/${escapeRegex(
|
||||
buildId
|
||||
|
@ -899,6 +937,14 @@ const runTests = (dev = false, looseMode = false) => {
|
|||
'^\\/blog\\/([^\\/]+?)\\/([^\\/]+?)(?:\\/)?$'
|
||||
),
|
||||
},
|
||||
'/lang/[lang]/about': {
|
||||
dataRoute: `/_next/data/${buildId}/lang/[lang]/about.json`,
|
||||
dataRouteRegex: normalizeRegEx(
|
||||
`^\\/_next\\/data\\/${escapedBuildId}\\/lang\\/([^\\/]+?)\\/about\\.json$`
|
||||
),
|
||||
fallback: false,
|
||||
routeRegex: normalizeRegEx('^\\/lang\\/([^\\/]+?)\\/about(?:\\/)?$'),
|
||||
},
|
||||
'/non-json/[p]': {
|
||||
dataRoute: `/_next/data/${buildId}/non-json/[p].json`,
|
||||
dataRouteRegex: normalizeRegEx(
|
||||
|
@ -1039,10 +1085,9 @@ const runTests = (dev = false, looseMode = false) => {
|
|||
}
|
||||
|
||||
describe('SSG Prerender', () => {
|
||||
afterAll(() => fs.remove(nextConfig))
|
||||
|
||||
describe('dev mode', () => {
|
||||
beforeAll(async () => {
|
||||
origConfig = await fs.readFile(nextConfig, 'utf8')
|
||||
await fs.writeFile(
|
||||
nextConfig,
|
||||
`
|
||||
|
@ -1053,6 +1098,10 @@ describe('SSG Prerender', () => {
|
|||
{
|
||||
source: "/some-rewrite/:item",
|
||||
destination: "/blog/post-:item"
|
||||
},
|
||||
{
|
||||
source: '/about',
|
||||
destination: '/lang/en/about'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -1069,13 +1118,17 @@ describe('SSG Prerender', () => {
|
|||
})
|
||||
buildId = 'development'
|
||||
})
|
||||
afterAll(() => killApp(app))
|
||||
afterAll(async () => {
|
||||
await fs.writeFile(nextConfig, origConfig)
|
||||
await killApp(app)
|
||||
})
|
||||
|
||||
runTests(true)
|
||||
})
|
||||
|
||||
describe('dev mode getStaticPaths', () => {
|
||||
beforeAll(async () => {
|
||||
origConfig = await fs.readFile(nextConfig, 'utf8')
|
||||
await fs.writeFile(
|
||||
nextConfig,
|
||||
// we set cpus to 1 so that we make sure the requests
|
||||
|
@ -1090,7 +1143,7 @@ describe('SSG Prerender', () => {
|
|||
})
|
||||
})
|
||||
afterAll(async () => {
|
||||
await fs.remove(nextConfig)
|
||||
await fs.writeFile(nextConfig, origConfig)
|
||||
await killApp(app)
|
||||
})
|
||||
|
||||
|
@ -1133,6 +1186,7 @@ describe('SSG Prerender', () => {
|
|||
beforeAll(async () => {
|
||||
// remove firebase import since it breaks in legacy serverless mode
|
||||
origBlogPageContent = await fs.readFile(blogPagePath, 'utf8')
|
||||
origConfig = await fs.readFile(nextConfig, 'utf8')
|
||||
|
||||
await fs.writeFile(
|
||||
blogPagePath,
|
||||
|
@ -1144,7 +1198,19 @@ describe('SSG Prerender', () => {
|
|||
|
||||
await fs.writeFile(
|
||||
nextConfig,
|
||||
`module.exports = { target: 'serverless' }`,
|
||||
`module.exports = {
|
||||
target: 'serverless',
|
||||
experimental: {
|
||||
rewrites() {
|
||||
return [
|
||||
{
|
||||
source: '/about',
|
||||
destination: '/lang/en/about'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}`,
|
||||
'utf8'
|
||||
)
|
||||
await fs.remove(join(appDir, '.next'))
|
||||
|
@ -1160,6 +1226,7 @@ describe('SSG Prerender', () => {
|
|||
buildId = await fs.readFile(join(appDir, '.next/BUILD_ID'), 'utf8')
|
||||
})
|
||||
afterAll(async () => {
|
||||
await fs.writeFile(nextConfig, origConfig)
|
||||
await fs.writeFile(blogPagePath, origBlogPageContent)
|
||||
await killApp(app)
|
||||
})
|
||||
|
@ -1234,6 +1301,7 @@ describe('SSG Prerender', () => {
|
|||
return initNextServerScript(scriptPath, /ready on/i, env)
|
||||
}
|
||||
|
||||
origConfig = await fs.readFile(nextConfig, 'utf8')
|
||||
await fs.writeFile(
|
||||
nextConfig,
|
||||
`module.exports = { target: 'experimental-serverless-trace' }`,
|
||||
|
@ -1249,7 +1317,10 @@ describe('SSG Prerender', () => {
|
|||
appPort = await findPort()
|
||||
app = await startServerlessEmulator(appDir, appPort, buildId)
|
||||
})
|
||||
afterAll(() => killApp(app))
|
||||
afterAll(async () => {
|
||||
await fs.writeFile(nextConfig, origConfig)
|
||||
await killApp(app)
|
||||
})
|
||||
|
||||
runTests(false, true)
|
||||
})
|
||||
|
@ -1257,7 +1328,6 @@ describe('SSG Prerender', () => {
|
|||
describe('production mode', () => {
|
||||
let buildOutput = ''
|
||||
beforeAll(async () => {
|
||||
await fs.remove(nextConfig)
|
||||
await fs.remove(join(appDir, '.next'))
|
||||
const { stdout } = await nextBuild(appDir, [], { stdout: true })
|
||||
buildOutput = stdout
|
||||
|
@ -1296,6 +1366,7 @@ describe('SSG Prerender', () => {
|
|||
|
||||
beforeAll(async () => {
|
||||
exportDir = join(appDir, 'out')
|
||||
origConfig = await fs.readFile(nextConfig, 'utf8')
|
||||
await fs.writeFile(
|
||||
nextConfig,
|
||||
`module.exports = {
|
||||
|
@ -1329,8 +1400,8 @@ describe('SSG Prerender', () => {
|
|||
buildId = await fs.readFile(join(appDir, '.next/BUILD_ID'), 'utf8')
|
||||
})
|
||||
afterAll(async () => {
|
||||
await fs.writeFile(nextConfig, origConfig)
|
||||
await stopApp(app)
|
||||
await fs.remove(nextConfig)
|
||||
|
||||
for (const page of fallbackTruePages) {
|
||||
const pagePath = join(appDir, 'pages', page)
|
||||
|
|
Loading…
Reference in a new issue