Skip to content

Commit 341b98b

Browse files
committed
fix: not-found from app router should work & be prioritized when pages i18n is used
1 parent 18c6710 commit 341b98b

File tree

8 files changed

+186
-0
lines changed

8 files changed

+186
-0
lines changed

packages/next/src/build/index.ts

+10
Original file line numberDiff line numberDiff line change
@@ -3294,6 +3294,16 @@ export default async function build(
32943294
orig,
32953295
path.join(distDir, 'server', updatedRelativeDest)
32963296
)
3297+
3298+
// since the app router not found is prioritized over pages router,
3299+
// we have to ensure the app router entries are available for all locales
3300+
if (i18n) {
3301+
for (const locale of i18n.locales) {
3302+
const curPath = `/${locale}/404`
3303+
pagesManifest[curPath] = updatedRelativeDest
3304+
}
3305+
}
3306+
32973307
pagesManifest['/404'] = updatedRelativeDest
32983308
}
32993309
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { notFound } from 'next/navigation'
2+
3+
export async function generateStaticParams() {
4+
return []
5+
}
6+
7+
async function validateSlug(slug: string[]) {
8+
try {
9+
const isValidPath =
10+
slug.length === 1 && (slug[0] === 'about' || slug[0] === 'contact')
11+
12+
if (!isValidPath) {
13+
return false
14+
}
15+
16+
return true
17+
} catch (error) {
18+
throw error
19+
}
20+
}
21+
22+
export default async function CatchAll({
23+
params,
24+
}: {
25+
params: Promise<{ slug: string[] }>
26+
}) {
27+
const { slug } = await params
28+
const slugArray = Array.isArray(slug) ? slug : [slug]
29+
30+
// Validate the slug
31+
const isValid = await validateSlug(slugArray)
32+
33+
// If not valid, show 404
34+
if (!isValid) {
35+
notFound()
36+
}
37+
38+
return (
39+
<div>
40+
<h1>Catch All</h1>
41+
<p>This is a catch all page added to the APP router</p>
42+
</div>
43+
)
44+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
export const metadata = {
2+
title: 'Next.js',
3+
description: 'Generated by Next.js',
4+
}
5+
6+
export default function RootLayout({
7+
children,
8+
}: {
9+
children: React.ReactNode
10+
}) {
11+
return (
12+
<html lang="en">
13+
<body>{children}</body>
14+
</html>
15+
)
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import React from 'react'
2+
3+
const NotFound = () => (
4+
<div>
5+
<h1>APP ROUTER - 404 PAGE</h1>
6+
<p>This page is using the APP ROUTER</p>
7+
</div>
8+
)
9+
10+
export default NotFound
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/**
2+
* @type {import('next').NextConfig}
3+
*/
4+
const nextConfig = {
5+
i18n: {
6+
locales: ['en-GB', 'en'],
7+
defaultLocale: 'en',
8+
localeDetection: false,
9+
},
10+
}
11+
12+
module.exports = nextConfig
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { nextTestSetup } from 'e2e-utils'
2+
3+
describe('not-found-with-pages', () => {
4+
const { next, isNextStart } = nextTestSetup({
5+
files: __dirname,
6+
})
7+
8+
if (isNextStart) {
9+
it('should write all locales to the pages manifest', async () => {
10+
const pagesManifest = JSON.parse(
11+
await next.readFile('.next/server/pages-manifest.json')
12+
)
13+
14+
expect(pagesManifest['/404']).toBe('pages/404.html')
15+
expect(pagesManifest['/en/404']).toBe('pages/404.html')
16+
expect(pagesManifest['/en-GB/404']).toBe('pages/404.html')
17+
})
18+
}
19+
20+
it('should prefer the app router 404 over the pages router 404 when both are present', async () => {
21+
const browser = await next.browser('/app-dir/foo')
22+
expect(await browser.elementByCss('h1').text()).toBe(
23+
'APP ROUTER - 404 PAGE'
24+
)
25+
26+
await browser.loadPage(next.url)
27+
expect(await browser.elementByCss('h1').text()).toBe(
28+
'APP ROUTER - 404 PAGE'
29+
)
30+
})
31+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import React from 'react'
2+
import { GetStaticProps } from 'next'
3+
4+
interface NotFoundProps {
5+
message: string
6+
}
7+
8+
const NotFound = ({ message }: NotFoundProps) => (
9+
<div>
10+
<h1>PAGES ROUTER - 404 PAGE</h1>
11+
<p>This page is using the PAGES ROUTER</p>
12+
<p>{message}</p>
13+
</div>
14+
)
15+
16+
export const getStaticProps: GetStaticProps<NotFoundProps> = async () => {
17+
return {
18+
props: {
19+
message: 'Custom message fetched at build time',
20+
},
21+
}
22+
}
23+
24+
export default NotFound
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import React from 'react'
2+
3+
export const getStaticProps = ({ params }: { params: { slug: string[] } }) => {
4+
try {
5+
const slugArray = Array.isArray(params.slug) ? params.slug : [params.slug]
6+
7+
const isValidPath =
8+
slugArray.length === 1 &&
9+
(slugArray[0] === 'about' || slugArray[0] === 'contact')
10+
11+
if (!isValidPath) {
12+
return {
13+
notFound: true,
14+
}
15+
}
16+
17+
return {
18+
props: {
19+
slug: params.slug,
20+
},
21+
}
22+
} catch (error) {
23+
throw error
24+
}
25+
}
26+
27+
export const getStaticPaths = async () => ({
28+
paths: [],
29+
fallback: 'blocking',
30+
})
31+
32+
const CatchAll = () => (
33+
<div>
34+
<h1>Catch All</h1>
35+
<p>This is a catch all page added to the pages router</p>
36+
</div>
37+
)
38+
39+
export default CatchAll

0 commit comments

Comments
 (0)