Skip to content

Commit 1fefb4a

Browse files
huozhialexkirszsokra
authored
Reland "Refine the not-found rendering process for app router" (#52985)
Reland #52790 Reverts #52977 was failed due to failed job [vercel/next.js/actions/runs/5616458194/job/15220295829](https://github.com/vercel/next.js/actions/runs/5616458194/job/15220295829) Should be fine to resolve with #52979 now Fixes #52718 Fixes #52739 --------- Co-authored-by: Alex Kirszenberg <[email protected]> Co-authored-by: Tobias Koppers <[email protected]>
1 parent 732219e commit 1fefb4a

File tree

29 files changed

+448
-280
lines changed

29 files changed

+448
-280
lines changed

packages/next-swc/crates/next-core/js/src/dev/hot-reloader.tsx

+2-21
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,12 @@ import { useRouter, usePathname } from 'next/dist/client/components/navigation'
55
import { useEffect } from 'react'
66
import { subscribeToUpdate } from '@vercel/turbopack-ecmascript-runtime/dev/client/hmr-client'
77
import { ReactDevOverlay } from './client'
8-
import { NotFoundBoundary } from 'next/dist/client/components/not-found-boundary'
98

109
type HotReloadProps = React.PropsWithChildren<{
1110
assetPrefix?: string
12-
notFound?: React.ReactNode
13-
notFoundStyles?: React.ReactNode
14-
asNotFound?: boolean
1511
}>
1612

17-
export default function HotReload({
18-
assetPrefix,
19-
children,
20-
notFound,
21-
notFoundStyles,
22-
asNotFound,
23-
}: HotReloadProps) {
13+
export default function HotReload({ children }: HotReloadProps) {
2414
const router = useRouter()
2515
const path = usePathname()!.slice(1)
2616

@@ -41,14 +31,5 @@ export default function HotReload({
4131
return unsubscribe
4232
}, [router, path])
4333

44-
return (
45-
<NotFoundBoundary
46-
key={asNotFound + ''}
47-
notFound={notFound}
48-
notFoundStyles={notFoundStyles}
49-
asNotFound={asNotFound}
50-
>
51-
<ReactDevOverlay globalOverlay={true}>{children}</ReactDevOverlay>
52-
</NotFoundBoundary>
53-
)
34+
return <ReactDevOverlay globalOverlay={true}>{children}</ReactDevOverlay>
5435
}

packages/next-swc/crates/next-core/js/src/overlay/internal/ReactDevOverlay.tsx

-5
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import { ErrorBoundary } from './ErrorBoundary'
77
import { Base } from './styles/Base'
88
import { ComponentStyles } from './styles/ComponentStyles'
99
import { CssReset } from './styles/CssReset'
10-
import { notFound } from 'next/dist/client/components/not-found'
1110

1211
type RefreshState =
1312
| {
@@ -210,10 +209,6 @@ export default function ReactDevOverlay({
210209

211210
const isMounted = hasBuildError || hasRuntimeErrors
212211

213-
if (state.notFound) {
214-
notFound()
215-
}
216-
217212
return (
218213
<React.Fragment>
219214
<ErrorBoundary

packages/next-swc/crates/next-core/src/app_source.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -601,7 +601,7 @@ pub async fn create_app_source(
601601
)))
602602
.collect();
603603

604-
if let Some(&Entrypoint::AppPage { loader_tree }) = entrypoints.get("/") {
604+
if let Some(&Entrypoint::AppPage { loader_tree }) = entrypoints.get("/_not-found") {
605605
if loader_tree.await?.components.await?.not_found.is_some() {
606606
// Only add a source for the app 404 page if a top-level not-found page is
607607
// defined. Otherwise, the 404 page is handled by the pages logic.

packages/next-swc/crates/next-core/src/app_structure.rs

+11-4
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use turbopack_binding::{
1414
turbopack::core::issue::{Issue, IssueExt, IssueSeverity},
1515
};
1616

17-
use crate::next_config::NextConfig;
17+
use crate::{next_config::NextConfig, next_import_map::get_next_package};
1818

1919
/// A final route in the app directory.
2020
#[turbo_tasks::value]
@@ -675,9 +675,16 @@ async fn directory_tree_to_entrypoints_internal(
675675
LoaderTree {
676676
segment: directory_name.to_string(),
677677
parallel_routes: indexmap! {
678-
// TODO(alexkirsz) Next.js has a __DEFAULT__ entry for
679-
// next/dist/client/components/parallel-route-default
680-
// here.
678+
"children".to_string() => LoaderTree {
679+
segment: "__DEFAULT__".to_string(),
680+
parallel_routes: IndexMap::new(),
681+
components: Components {
682+
default: Some(get_next_package(app_dir).join("dist/client/components/parallel-route-default.js".to_string())),
683+
..Default::default()
684+
}
685+
.cell(),
686+
}
687+
.cell(),
681688
},
682689
components: components.without_leafs().cell(),
683690
}

packages/next-swc/crates/next-dev-tests/tests/integration.rs

+11-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#![cfg(test)]
44

55
use std::{
6+
collections::{hash_map::Entry, HashMap},
67
env,
78
fmt::Write,
89
future::{pending, Future},
@@ -34,6 +35,7 @@ use next_core::turbopack::{
3435
};
3536
use next_dev::{EntryRequest, NextDevServerBuilder};
3637
use owo_colors::OwoColorize;
38+
use parking_lot::Mutex;
3739
use regex::{Captures, Regex, Replacer};
3840
use serde::Deserialize;
3941
use tempdir::TempDir;
@@ -637,10 +639,12 @@ struct ChangeFileCommand {
637639
replace_with: String,
638640
}
639641

640-
#[turbo_tasks::value(shared)]
642+
#[turbo_tasks::value(shared, serialization = "none", eq = "manual", cell = "new")]
641643
struct TestIssueReporter {
642644
#[turbo_tasks(trace_ignore, debug_ignore)]
643645
pub issue_tx: State<UnboundedSender<(ReadRef<PlainIssue>, ReadRef<ValueDebugString>)>>,
646+
#[turbo_tasks(trace_ignore, debug_ignore)]
647+
pub already_printed: Mutex<HashMap<String, ()>>,
644648
}
645649

646650
#[turbo_tasks::value_impl]
@@ -653,6 +657,7 @@ impl TestIssueReporter {
653657
) -> Vc<Self> {
654658
TestIssueReporter {
655659
issue_tx: State::new((*issue_tx).clone()),
660+
already_printed: Default::default(),
656661
}
657662
.cell()
658663
}
@@ -678,7 +683,11 @@ impl IssueReporter for TestIssueReporter {
678683
for (issue, path) in captured_issues.iter_with_shortest_path() {
679684
let plain = NormalizedIssue(issue).cell().into_plain(path);
680685
issue_tx.send((plain.await?, plain.dbg().await?))?;
681-
println!("{}", format_issue(&*plain.await?, None, &log_options));
686+
let str = format_issue(&*plain.await?, None, &log_options);
687+
if let Entry::Vacant(e) = self.already_printed.lock().entry(str) {
688+
println!("{}", e.key());
689+
e.insert(());
690+
}
682691
}
683692
Ok(Vc::cell(false))
684693
}

packages/next-swc/crates/next-dev-tests/tests/integration/next/app/404-custom/input/app/test.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ function runTests(harness: Harness, iframe: HTMLIFrameElement) {
4848
TIMEOUT
4949
)
5050

51+
// TODO: This test is flaky, so it needs a particularly long timeout.
5152
it(
5253
'renders a custom 404 page',
5354
async () => {
@@ -58,7 +59,7 @@ function runTests(harness: Harness, iframe: HTMLIFrameElement) {
5859
iframe.contentDocument!.querySelector('[data-test-notfound]')
5960
).not.toBeNull()
6061
},
61-
TIMEOUT
62+
LONG_TIMEOUT
6263
)
6364

6465
// TODO: This test is flaky, so it needs a particularly long timeout.

packages/next/src/client/components/app-router.tsx

+14-27
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ import { isBot } from '../../shared/lib/router/utils/is-bot'
5454
import { addBasePath } from '../add-base-path'
5555
import { AppRouterAnnouncer } from './app-router-announcer'
5656
import { RedirectBoundary } from './redirect-boundary'
57-
import { NotFoundBoundary } from './not-found-boundary'
5857
import { findHeadInCache } from './router-reducer/reducers/find-head-in-cache'
5958
import { createInfinitePromise } from './infinite-promise'
6059
import { NEXT_RSC_UNION_QUERY } from './app-router-headers'
@@ -89,24 +88,13 @@ export function urlToUrlWithoutFlightMarker(url: string): URL {
8988
return urlWithoutFlightParameters
9089
}
9190

92-
const HotReloader:
93-
| typeof import('./react-dev-overlay/hot-reloader-client').default
94-
| null =
95-
process.env.NODE_ENV === 'production'
96-
? null
97-
: (require('./react-dev-overlay/hot-reloader-client')
98-
.default as typeof import('./react-dev-overlay/hot-reloader-client').default)
99-
10091
type AppRouterProps = Omit<
10192
Omit<InitialRouterStateParameters, 'isServer' | 'location'>,
10293
'initialParallelRoutes'
10394
> & {
10495
buildId: string
10596
initialHead: ReactNode
10697
assetPrefix: string
107-
// Top level boundaries props
108-
notFound: React.ReactNode | undefined
109-
asNotFound?: boolean
11098
}
11199

112100
function isExternalURL(url: URL) {
@@ -224,8 +212,6 @@ function Router({
224212
initialCanonicalUrl,
225213
children,
226214
assetPrefix,
227-
notFound,
228-
asNotFound,
229215
}: AppRouterProps) {
230216
const initialState = useMemo(
231217
() =>
@@ -445,16 +431,26 @@ function Router({
445431
return findHeadInCache(cache, tree[1])
446432
}, [cache, tree])
447433

448-
const notFoundProps = { notFound, asNotFound }
449-
450-
const content = (
434+
let content = (
451435
<RedirectBoundary>
452436
{head}
453437
{cache.subTreeData}
454438
<AppRouterAnnouncer tree={tree} />
455439
</RedirectBoundary>
456440
)
457441

442+
if (process.env.NODE_ENV !== 'production') {
443+
if (typeof window !== 'undefined') {
444+
const DevRootNotFoundBoundary: typeof import('./dev-root-not-found-boundary').DevRootNotFoundBoundary =
445+
require('./dev-root-not-found-boundary').DevRootNotFoundBoundary
446+
content = <DevRootNotFoundBoundary>{content}</DevRootNotFoundBoundary>
447+
}
448+
const HotReloader: typeof import('./react-dev-overlay/hot-reloader-client').default =
449+
require('./react-dev-overlay/hot-reloader-client').default
450+
451+
content = <HotReloader assetPrefix={assetPrefix}>{content}</HotReloader>
452+
}
453+
458454
return (
459455
<>
460456
<HistoryUpdater
@@ -484,16 +480,7 @@ function Router({
484480
url: canonicalUrl,
485481
}}
486482
>
487-
{HotReloader ? (
488-
// HotReloader implements a separate NotFoundBoundary to maintain the HMR ping interval
489-
<HotReloader assetPrefix={assetPrefix} {...notFoundProps}>
490-
{content}
491-
</HotReloader>
492-
) : (
493-
<NotFoundBoundary {...notFoundProps}>
494-
{content}
495-
</NotFoundBoundary>
496-
)}
483+
{content}
497484
</LayoutRouterContext.Provider>
498485
</AppRouterContext.Provider>
499486
</GlobalLayoutRouterContext.Provider>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
'use client'
2+
3+
import React from 'react'
4+
import { NotFoundBoundary } from './not-found-boundary'
5+
6+
export function bailOnNotFound() {
7+
throw new Error('notFound() is not allowed to use in root layout')
8+
}
9+
10+
function NotAllowedRootNotFoundError() {
11+
bailOnNotFound()
12+
return null
13+
}
14+
15+
export function DevRootNotFoundBoundary({
16+
children,
17+
}: {
18+
children: React.ReactNode
19+
}) {
20+
return (
21+
<NotFoundBoundary notFound={<NotAllowedRootNotFoundError />}>
22+
{children}
23+
</NotFoundBoundary>
24+
)
25+
}

packages/next/src/client/components/layout-router.tsx

-3
Original file line numberDiff line numberDiff line change
@@ -491,7 +491,6 @@ export default function OuterLayoutRouter({
491491
template,
492492
notFound,
493493
notFoundStyles,
494-
asNotFound,
495494
styles,
496495
}: {
497496
parallelRouterKey: string
@@ -506,7 +505,6 @@ export default function OuterLayoutRouter({
506505
hasLoading: boolean
507506
notFound: React.ReactNode | undefined
508507
notFoundStyles: React.ReactNode | undefined
509-
asNotFound?: boolean
510508
styles?: React.ReactNode
511509
}) {
512510
const context = useContext(LayoutRouterContext)
@@ -574,7 +572,6 @@ export default function OuterLayoutRouter({
574572
<NotFoundBoundary
575573
notFound={notFound}
576574
notFoundStyles={notFoundStyles}
577-
asNotFound={asNotFound}
578575
>
579576
<RedirectBoundary>
580577
<InnerLayoutRouter

packages/next/src/client/components/not-found-boundary.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ class NotFoundErrorBoundary extends React.Component<
6464
return (
6565
<>
6666
<meta name="robots" content="noindex" />
67+
{process.env.NODE_ENV === 'development' && (
68+
<meta name="next-error" content="not-found" />
69+
)}
6770
{this.props.notFoundStyles}
6871
{this.props.notFound}
6972
</>

0 commit comments

Comments
 (0)