Skip to content

Commit

Permalink
Add support for rewriting non-fallback SSG pages (#11010)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
ijjk committed May 29, 2020
1 parent 6a993d5 commit ae3c388
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 10 deletions.
9 changes: 7 additions & 2 deletions packages/next/next-server/server/next-server.ts
Expand Up @@ -580,6 +580,7 @@ export default class Server {
}
}
;(req as any)._nextDidRewrite = true
;(req as any)._nextRewroteUrl = newUrl

return {
finished: false,
Expand Down Expand Up @@ -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
Expand Down
12 changes: 12 additions & 0 deletions test/integration/prerender/next.config.js
@@ -0,0 +1,12 @@
module.exports = {
experimental: {
rewrites() {
return [
{
source: '/about',
destination: '/lang/en/about',
},
]
},
},
}
12 changes: 12 additions & 0 deletions test/integration/prerender/pages/lang/[lang]/about.js
@@ -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,
})
87 changes: 79 additions & 8 deletions test/integration/prerender/test/index.test.js
Expand Up @@ -36,6 +36,7 @@ let buildId
let distPagesDir
let exportDir
let stderr
let origConfig

const startServer = async (optEnv = {}) => {
const scriptPath = join(appDir, 'server.js')
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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,
`
Expand All @@ -1053,6 +1098,10 @@ describe('SSG Prerender', () => {
{
source: "/some-rewrite/:item",
destination: "/blog/post-:item"
},
{
source: '/about',
destination: '/lang/en/about'
}
]
}
Expand All @@ -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
Expand All @@ -1090,7 +1143,7 @@ describe('SSG Prerender', () => {
})
})
afterAll(async () => {
await fs.remove(nextConfig)
await fs.writeFile(nextConfig, origConfig)
await killApp(app)
})

Expand Down Expand Up @@ -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,
Expand All @@ -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'))
Expand All @@ -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)
})
Expand Down Expand Up @@ -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' }`,
Expand All @@ -1249,15 +1317,17 @@ 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)
})

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
Expand Down Expand Up @@ -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 = {
Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit ae3c388

Please sign in to comment.