Skip to content

Commit 1697852

Browse files
authored
Fix revalidate for initial notFound: true paths (#28097)
This fixes revalidation not occurring correctly when `notFound: true` is returned during build, additional tests have been added to ensure this is working correctly for dynamic and non-dynamic pages returning `notFound: true` during build and then revalidating afterwards. ## Bug - [x] Related issues linked using `fixes #number` - [x] Integration tests added - [x] Errors have helpful link attached, see `contributing.md` Fixes: #21453
1 parent 8a80b0b commit 1697852

File tree

7 files changed

+128
-8
lines changed

7 files changed

+128
-8
lines changed

packages/next/server/incremental-cache.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,12 @@ export class IncrementalCache {
139139
// let's check the disk for seed data
140140
if (!data) {
141141
if (this.prerenderManifest.notFoundRoutes.includes(pathname)) {
142-
return { revalidateAfter: false, value: null }
142+
const now = Date.now()
143+
const revalidateAfter = this.calculateRevalidate(pathname, now)
144+
data = {
145+
value: null,
146+
revalidateAfter: revalidateAfter !== false ? now : false,
147+
}
143148
}
144149

145150
try {

packages/next/server/render.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -827,11 +827,6 @@ export async function renderToHTML(
827827
;(data as any).revalidate = false
828828
}
829829

830-
// this must come after revalidate is attached
831-
if ((renderOpts as any).isNotFound) {
832-
return null
833-
}
834-
835830
props.pageProps = Object.assign(
836831
{},
837832
props.pageProps,
@@ -843,6 +838,11 @@ export async function renderToHTML(
843838
;(renderOpts as any).revalidate =
844839
'revalidate' in data ? data.revalidate : undefined
845840
;(renderOpts as any).pageData = props
841+
842+
// this must come after revalidate is added to renderOpts
843+
if ((renderOpts as any).isNotFound) {
844+
return null
845+
}
846846
}
847847

848848
if (getServerSideProps) {
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
404

test/integration/not-found-revalidate/pages/404.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export default function Page(props) {
88
}
99

1010
export const getStaticProps = () => {
11+
console.log('404 getStaticProps')
1112
return {
1213
props: {
1314
notFound: true,
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import fs from 'fs'
2+
import path from 'path'
3+
4+
export async function getStaticProps() {
5+
const data = await fs.promises.readFile(
6+
path.join(process.cwd(), 'data.txt'),
7+
'utf8'
8+
)
9+
10+
console.log('revalidate', { data })
11+
12+
return {
13+
props: { data },
14+
notFound: data.trim() === '404',
15+
revalidate: 1,
16+
}
17+
}
18+
19+
export async function getStaticPaths() {
20+
return {
21+
paths: [{ params: { slug: 'first' } }],
22+
fallback: 'blocking',
23+
}
24+
}
25+
26+
export default function Page({ data }) {
27+
return <p id="data">{data}</p>
28+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import fs from 'fs'
2+
import path from 'path'
3+
4+
export async function getStaticProps() {
5+
const data = await fs.promises.readFile(
6+
path.join(process.cwd(), 'data.txt'),
7+
'utf8'
8+
)
9+
10+
console.log('revalidate', { data })
11+
12+
return {
13+
props: { data },
14+
notFound: data.trim() === '404',
15+
revalidate: 1,
16+
}
17+
}
18+
19+
export default function Page({ data }) {
20+
return <p id="data">{data}</p>
21+
}

test/integration/not-found-revalidate/test/index.test.js

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,74 @@ import {
1010
killApp,
1111
fetchViaHTTP,
1212
waitFor,
13+
check,
1314
} from 'next-test-utils'
1415

1516
jest.setTimeout(1000 * 60 * 2)
1617
const appDir = join(__dirname, '..')
18+
const dataFile = join(appDir, 'data.txt')
19+
1720
let app
1821
let appPort
1922

2023
const runTests = () => {
24+
it('should revalidate page when notFund returned during build', async () => {
25+
let res = await fetchViaHTTP(appPort, '/initial-not-found/first')
26+
let $ = cheerio.load(await res.text())
27+
expect(res.status).toBe(404)
28+
expect($('#not-found').text()).toBe('404 page')
29+
30+
res = await fetchViaHTTP(appPort, '/initial-not-found/second')
31+
$ = cheerio.load(await res.text())
32+
expect(res.status).toBe(404)
33+
expect($('#not-found').text()).toBe('404 page')
34+
35+
res = await fetchViaHTTP(appPort, '/initial-not-found')
36+
$ = cheerio.load(await res.text())
37+
expect(res.status).toBe(404)
38+
expect($('#not-found').text()).toBe('404 page')
39+
40+
await fs.writeFile(dataFile, '200')
41+
42+
// wait for revalidation period
43+
await waitFor(1500)
44+
await fetchViaHTTP(appPort, '/initial-not-found/first')
45+
await fetchViaHTTP(appPort, '/initial-not-found/second')
46+
await fetchViaHTTP(appPort, '/initial-not-found')
47+
48+
// wait for revalidation to occur in background
49+
try {
50+
await check(async () => {
51+
res = await fetchViaHTTP(appPort, '/initial-not-found/first')
52+
$ = cheerio.load(await res.text())
53+
54+
return res.status === 200 && $('#data').text() === '200'
55+
? 'success'
56+
: `${res.status} - ${$('#data').text()}`
57+
}, 'success')
58+
59+
await check(async () => {
60+
res = await fetchViaHTTP(appPort, '/initial-not-found/second')
61+
$ = cheerio.load(await res.text())
62+
63+
return res.status === 200 && $('#data').text() === '200'
64+
? 'success'
65+
: `${res.status} - ${$('#data').text()}`
66+
}, 'success')
67+
68+
await check(async () => {
69+
res = await fetchViaHTTP(appPort, '/initial-not-found')
70+
$ = cheerio.load(await res.text())
71+
72+
return res.status === 200 && $('#data').text() === '200'
73+
? 'success'
74+
: `${res.status} - ${$('#data').text()}`
75+
}, 'success')
76+
} finally {
77+
await fs.writeFile(dataFile, '404')
78+
}
79+
})
80+
2181
it('should revalidate after notFound is returned for fallback: blocking', async () => {
2282
let res = await fetchViaHTTP(appPort, '/fallback-blocking/hello')
2383
let $ = cheerio.load(await res.text())
@@ -138,9 +198,13 @@ describe('SSG notFound revalidate', () => {
138198
describe('production mode', () => {
139199
beforeAll(async () => {
140200
await fs.remove(join(appDir, '.next'))
141-
await nextBuild(appDir)
201+
await nextBuild(appDir, undefined, {
202+
cwd: appDir,
203+
})
142204
appPort = await findPort()
143-
app = await nextStart(appDir, appPort)
205+
app = await nextStart(appDir, appPort, {
206+
cwd: appDir,
207+
})
144208
})
145209
afterAll(() => killApp(app))
146210

0 commit comments

Comments
 (0)