Skip to content

Commit 3894320

Browse files
authored
Update check for fallback pages during export (#33323)
This fixes our check for fallback pages during `next export` as we are currently erroring even when the erroneous pages are not included in the `exportPathMap`. This also adds additional tests to certify the expected behavior for the error. ## Bug - [x] Related issues linked using `fixes #number` - [x] Integration tests added - [x] Errors have helpful link attached, see `contributing.md` Fixes: #29135
1 parent aaa77dd commit 3894320

File tree

7 files changed

+183
-26
lines changed

7 files changed

+183
-26
lines changed

packages/next/export/index.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -448,14 +448,12 @@ export default async function exportApp(
448448
if (prerenderManifest && !options.buildExport) {
449449
const fallbackEnabledPages = new Set()
450450

451-
for (const key of Object.keys(prerenderManifest.dynamicRoutes)) {
452-
// only error if page is included in path map
453-
if (!exportPathMap[key] && !excludedPrerenderRoutes.has(key)) {
454-
continue
455-
}
451+
for (const path of Object.keys(exportPathMap)) {
452+
const page = exportPathMap[path].page
453+
const prerenderInfo = prerenderManifest.dynamicRoutes[page]
456454

457-
if (prerenderManifest.dynamicRoutes[key].fallback !== false) {
458-
fallbackEnabledPages.add(key)
455+
if (prerenderInfo && prerenderInfo.fallback !== false) {
456+
fallbackEnabledPages.add(page)
459457
}
460458
}
461459

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module.exports = {
2+
exportPathMap() {
3+
return {
4+
'/first': { page: '/[slug]' },
5+
}
6+
},
7+
}

test/lib/next-modes/base.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,9 @@ export class NextInstance {
148148
}
149149
}
150150

151+
public async export(): Promise<{ exitCode?: number; cliOutput?: string }> {
152+
return {}
153+
}
151154
public async setup(): Promise<void> {}
152155
public async start(): Promise<void> {}
153156
public async stop(): Promise<void> {

test/lib/next-modes/next-start.ts

Lines changed: 58 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { spawn, SpawnOptions } from 'child_process'
66
export class NextStartInstance extends NextInstance {
77
private _buildId: string
88
private _cliOutput: string
9+
private spawnOpts: SpawnOptions
910

1011
public get buildId() {
1112
return this._buildId
@@ -19,11 +20,26 @@ export class NextStartInstance extends NextInstance {
1920
await super.createTestDir()
2021
}
2122

23+
private handleStdio = (childProcess) => {
24+
childProcess.stdout.on('data', (chunk) => {
25+
const msg = chunk.toString()
26+
process.stdout.write(chunk)
27+
this._cliOutput += msg
28+
this.emit('stdout', [msg])
29+
})
30+
childProcess.stderr.on('data', (chunk) => {
31+
const msg = chunk.toString()
32+
process.stderr.write(chunk)
33+
this._cliOutput += msg
34+
this.emit('stderr', [msg])
35+
})
36+
}
37+
2238
public async start() {
2339
if (this.childProcess) {
2440
throw new Error('next already started')
2541
}
26-
const spawnOpts: SpawnOptions = {
42+
this.spawnOpts = {
2743
cwd: this.testDir,
2844
stdio: ['ignore', 'pipe', 'pipe'],
2945
shell: false,
@@ -34,20 +50,6 @@ export class NextStartInstance extends NextInstance {
3450
__NEXT_RAND_PORT: '1',
3551
},
3652
}
37-
const handleStdio = () => {
38-
this.childProcess.stdout.on('data', (chunk) => {
39-
const msg = chunk.toString()
40-
process.stdout.write(chunk)
41-
this._cliOutput += msg
42-
this.emit('stdout', [msg])
43-
})
44-
this.childProcess.stderr.on('data', (chunk) => {
45-
const msg = chunk.toString()
46-
process.stderr.write(chunk)
47-
this._cliOutput += msg
48-
this.emit('stderr', [msg])
49-
})
50-
}
5153
let buildArgs = ['yarn', 'next', 'build']
5254
let startArgs = ['yarn', 'next', 'start']
5355

@@ -60,8 +62,12 @@ export class NextStartInstance extends NextInstance {
6062

6163
await new Promise<void>((resolve, reject) => {
6264
console.log('running', buildArgs.join(' '))
63-
this.childProcess = spawn(buildArgs[0], buildArgs.slice(1), spawnOpts)
64-
handleStdio()
65+
this.childProcess = spawn(
66+
buildArgs[0],
67+
buildArgs.slice(1),
68+
this.spawnOpts
69+
)
70+
this.handleStdio(this.childProcess)
6571
this.childProcess.on('exit', (code, signal) => {
6672
if (code || signal)
6773
reject(
@@ -85,8 +91,12 @@ export class NextStartInstance extends NextInstance {
8591
console.log('running', startArgs.join(' '))
8692

8793
await new Promise<void>((resolve) => {
88-
this.childProcess = spawn(startArgs[0], startArgs.slice(1), spawnOpts)
89-
handleStdio()
94+
this.childProcess = spawn(
95+
startArgs[0],
96+
startArgs.slice(1),
97+
this.spawnOpts
98+
)
99+
this.handleStdio(this.childProcess)
90100

91101
this.childProcess.on('close', (code, signal) => {
92102
if (this.isStopping) return
@@ -108,4 +118,33 @@ export class NextStartInstance extends NextInstance {
108118
this.on('stdout', readyCb)
109119
})
110120
}
121+
122+
public async export() {
123+
return new Promise((resolve) => {
124+
const curOutput = this._cliOutput.length
125+
const exportArgs = ['yarn', 'next', 'export']
126+
127+
if (this.childProcess) {
128+
throw new Error(
129+
`can not run export while server is running, use next.stop() first`
130+
)
131+
}
132+
console.log('running', exportArgs.join(' '))
133+
134+
this.childProcess = spawn(
135+
exportArgs[0],
136+
exportArgs.slice(1),
137+
this.spawnOpts
138+
)
139+
this.handleStdio(this.childProcess)
140+
141+
this.childProcess.on('exit', (code, signal) => {
142+
this.childProcess = undefined
143+
resolve({
144+
exitCode: signal || code,
145+
cliOutput: this.cliOutput.substr(curOutput),
146+
})
147+
})
148+
})
149+
}
111150
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { createNext, FileRef } from 'e2e-utils'
2+
import { NextInstance } from 'test/lib/next-modes/base'
3+
import { renderViaHTTP } from 'next-test-utils'
4+
import { join } from 'path'
5+
6+
describe('fallback export error', () => {
7+
let next: NextInstance
8+
9+
beforeAll(async () => {
10+
next = await createNext({
11+
files: {
12+
pages: new FileRef(join(__dirname, 'pages')),
13+
},
14+
})
15+
})
16+
afterAll(() => next.destroy())
17+
18+
it('should have built', async () => {
19+
const html = await renderViaHTTP(next.url, '/')
20+
expect(html).toContain('index page')
21+
})
22+
23+
it('should not error with default exportPathMap', async () => {
24+
await next.stop()
25+
26+
const result = await next.export()
27+
console.log(result.cliOutput)
28+
29+
expect(result.exitCode).toBe(0)
30+
expect(result.cliOutput).not.toContain(
31+
'Found pages with `fallback` enabled'
32+
)
33+
})
34+
35+
it('should not error with valid exportPathMap', async () => {
36+
await next.stop()
37+
await next.patchFile(
38+
'next.config.js',
39+
`
40+
module.exports = {
41+
exportPathMap() {
42+
return {
43+
'/': { page: '/' },
44+
}
45+
}
46+
}
47+
`
48+
)
49+
50+
try {
51+
const result = await next.export()
52+
console.log(result.cliOutput)
53+
54+
expect(result.exitCode).toBe(0)
55+
expect(result.cliOutput).not.toContain(
56+
'Found pages with `fallback` enabled'
57+
)
58+
} finally {
59+
await next.deleteFile('next.config.js')
60+
}
61+
})
62+
63+
it('should error with invalid exportPathMap', async () => {
64+
await next.stop()
65+
await next.patchFile(
66+
'next.config.js',
67+
`
68+
module.exports = {
69+
exportPathMap() {
70+
return {
71+
'/': { page: '/' },
72+
'/second': { page: '/[...slug]' }
73+
}
74+
}
75+
}
76+
`
77+
)
78+
79+
try {
80+
const result = await next.export()
81+
console.log(result.cliOutput)
82+
83+
expect(result.exitCode).toBe(1)
84+
expect(result.cliOutput).toContain('Found pages with `fallback` enabled')
85+
} finally {
86+
await next.deleteFile('next.config.js')
87+
}
88+
})
89+
})
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
export function getStaticProps() {
2+
return {
3+
props: {
4+
now: Date.now(),
5+
},
6+
}
7+
}
8+
9+
export function getStaticPaths() {
10+
return {
11+
paths: ['/first'],
12+
fallback: 'blocking',
13+
}
14+
}
15+
16+
export default function Page() {
17+
return <p>catch-all page</p>
18+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default function Page() {
2+
return <p>index page</p>
3+
}

0 commit comments

Comments
 (0)