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:
JJ Kasper 2020-05-29 11:33:09 -05:00 committed by GitHub
parent 6a993d5972
commit ae3c388039
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 110 additions and 10 deletions

View file

@ -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

View file

@ -0,0 +1,12 @@
module.exports = {
experimental: {
rewrites() {
return [
{
source: '/about',
destination: '/lang/en/about',
},
]
},
},
}

View 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,
})

View file

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