Skip to content

Commit 7859963

Browse files
authored
Merge branch 'canary' into bump-turbopack
2 parents 6d702bc + 783acc5 commit 7859963

File tree

18 files changed

+211
-27
lines changed

18 files changed

+211
-27
lines changed

packages/next/src/client/components/error-boundary.tsx

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use client'
22

33
import React from 'react'
4+
import { usePathname } from './navigation'
45

56
const styles = {
67
error: {
@@ -36,19 +37,50 @@ export interface ErrorBoundaryProps {
3637
errorStyles?: React.ReactNode | undefined
3738
}
3839

40+
interface ErrorBoundaryHandlerProps extends ErrorBoundaryProps {
41+
pathname: string
42+
}
43+
44+
interface ErrorBoundaryHandlerState {
45+
error: Error | null
46+
previousPathname: string
47+
}
48+
3949
export class ErrorBoundaryHandler extends React.Component<
40-
ErrorBoundaryProps,
41-
{ error: Error | null }
50+
ErrorBoundaryHandlerProps,
51+
ErrorBoundaryHandlerState
4252
> {
43-
constructor(props: ErrorBoundaryProps) {
53+
constructor(props: ErrorBoundaryHandlerProps) {
4454
super(props)
45-
this.state = { error: null }
55+
this.state = { error: null, previousPathname: this.props.pathname }
4656
}
4757

4858
static getDerivedStateFromError(error: Error) {
4959
return { error }
5060
}
5161

62+
static getDerivedStateFromProps(
63+
props: ErrorBoundaryHandlerProps,
64+
state: ErrorBoundaryHandlerState
65+
): ErrorBoundaryHandlerState | null {
66+
/**
67+
* Handles reset of the error boundary when a navigation happens.
68+
* Ensures the error boundary does not stay enabled when navigating to a new page.
69+
* Approach of setState in render is safe as it checks the previous pathname and then overrides
70+
* it as outlined in https://react.dev/reference/react/useState#storing-information-from-previous-renders
71+
*/
72+
if (props.pathname !== state.previousPathname && state.error) {
73+
return {
74+
error: null,
75+
previousPathname: props.pathname,
76+
}
77+
}
78+
return {
79+
error: state.error,
80+
previousPathname: props.pathname,
81+
}
82+
}
83+
5284
reset = () => {
5385
this.setState({ error: null })
5486
}
@@ -105,9 +137,11 @@ export function ErrorBoundary({
105137
errorStyles,
106138
children,
107139
}: ErrorBoundaryProps & { children: React.ReactNode }): JSX.Element {
140+
const pathname = usePathname()
108141
if (errorComponent) {
109142
return (
110143
<ErrorBoundaryHandler
144+
pathname={pathname}
111145
errorComponent={errorComponent}
112146
errorStyles={errorStyles}
113147
>

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

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React from 'react'
2+
import { usePathname } from './navigation'
23

34
interface NotFoundBoundaryProps {
45
notFound?: React.ReactNode
@@ -7,13 +8,25 @@ interface NotFoundBoundaryProps {
78
children: React.ReactNode
89
}
910

11+
interface NotFoundErrorBoundaryProps extends NotFoundBoundaryProps {
12+
pathname: string
13+
}
14+
15+
interface NotFoundErrorBoundaryState {
16+
notFoundTriggered: boolean
17+
previousPathname: string
18+
}
19+
1020
class NotFoundErrorBoundary extends React.Component<
11-
NotFoundBoundaryProps,
12-
{ notFoundTriggered: boolean }
21+
NotFoundErrorBoundaryProps,
22+
NotFoundErrorBoundaryState
1323
> {
14-
constructor(props: NotFoundBoundaryProps) {
24+
constructor(props: NotFoundErrorBoundaryProps) {
1525
super(props)
16-
this.state = { notFoundTriggered: !!props.asNotFound }
26+
this.state = {
27+
notFoundTriggered: !!props.asNotFound,
28+
previousPathname: props.pathname,
29+
}
1730
}
1831

1932
static getDerivedStateFromError(error: any) {
@@ -24,6 +37,28 @@ class NotFoundErrorBoundary extends React.Component<
2437
throw error
2538
}
2639

40+
static getDerivedStateFromProps(
41+
props: NotFoundErrorBoundaryProps,
42+
state: NotFoundErrorBoundaryState
43+
): NotFoundErrorBoundaryState | null {
44+
/**
45+
* Handles reset of the error boundary when a navigation happens.
46+
* Ensures the error boundary does not stay enabled when navigating to a new page.
47+
* Approach of setState in render is safe as it checks the previous pathname and then overrides
48+
* it as outlined in https://react.dev/reference/react/useState#storing-information-from-previous-renders
49+
*/
50+
if (props.pathname !== state.previousPathname && state.notFoundTriggered) {
51+
return {
52+
notFoundTriggered: false,
53+
previousPathname: props.pathname,
54+
}
55+
}
56+
return {
57+
notFoundTriggered: state.notFoundTriggered,
58+
previousPathname: props.pathname,
59+
}
60+
}
61+
2762
render() {
2863
if (this.state.notFoundTriggered) {
2964
return (
@@ -45,8 +80,10 @@ export function NotFoundBoundary({
4580
asNotFound,
4681
children,
4782
}: NotFoundBoundaryProps) {
83+
const pathname = usePathname()
4884
return notFound ? (
4985
<NotFoundErrorBoundary
86+
pathname={pathname}
5087
notFound={notFound}
5188
notFoundStyles={notFoundStyles}
5289
asNotFound={asNotFound}

packages/next/src/export/index.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -228,12 +228,6 @@ export default async function exportApp(
228228
)
229229
}
230230

231-
if (nextConfig.experimental.serverActions) {
232-
throw new ExportError(
233-
`Server Actions are not supported with static export.`
234-
)
235-
}
236-
237231
const customRoutesDetected = ['rewrites', 'redirects', 'headers'].filter(
238232
(config) => typeof nextConfig[config] === 'function'
239233
)

packages/next/src/server/lib/patch-fetch.ts

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -91,18 +91,17 @@ export function patchFetch({
9191
// Error caused by malformed URL should be handled by native fetch
9292
url = undefined
9393
}
94+
const fetchUrl = url?.href ?? ''
9495
const fetchStart = Date.now()
9596
const method = init?.method?.toUpperCase() || 'GET'
9697

9798
return await getTracer().trace(
9899
AppRenderSpan.fetch,
99100
{
100101
kind: SpanKind.CLIENT,
101-
spanName: ['fetch', method, url?.toString() ?? input.toString()]
102-
.filter(Boolean)
103-
.join(' '),
102+
spanName: ['fetch', method, fetchUrl].filter(Boolean).join(' '),
104103
attributes: {
105-
'http.url': url?.toString(),
104+
'http.url': fetchUrl,
106105
'http.method': method,
107106
'net.peer.name': url?.hostname,
108107
'net.peer.port': url?.port || undefined,
@@ -175,7 +174,7 @@ export function patchFetch({
175174
typeof curRevalidate !== 'undefined'
176175
) {
177176
console.warn(
178-
`Warning: fetch for ${input.toString()} specified "cache: ${_cache}" and "revalidate: ${curRevalidate}", only one should be specified.`
177+
`Warning: fetch for ${fetchUrl} on ${staticGenerationStore.pathname} specified "cache: ${_cache}" and "revalidate: ${curRevalidate}", only one should be specified.`
179178
)
180179
_cache = undefined
181180
}
@@ -219,7 +218,7 @@ export function patchFetch({
219218
if (isOnlyNoStore) {
220219
if (_cache === 'force-cache' || revalidate === 0) {
221220
throw new Error(
222-
`cache: 'force-cache' used on fetch for ${input.toString()} with 'export const fetchCache = 'only-no-store'`
221+
`cache: 'force-cache' used on fetch for ${fetchUrl} with 'export const fetchCache = 'only-no-store'`
223222
)
224223
}
225224
revalidate = 0
@@ -228,7 +227,7 @@ export function patchFetch({
228227

229228
if (isOnlyCache && _cache === 'no-store') {
230229
throw new Error(
231-
`cache: 'no-store' used on fetch for ${input.toString()} with 'export const fetchCache = 'only-cache'`
230+
`cache: 'no-store' used on fetch for ${fetchUrl} with 'export const fetchCache = 'only-cache'`
232231
)
233232
}
234233

@@ -284,7 +283,7 @@ export function patchFetch({
284283
try {
285284
cacheKey =
286285
await staticGenerationStore.incrementalCache.fetchCacheKey(
287-
isRequestInput ? (input as Request).url : input.toString(),
286+
fetchUrl,
288287
isRequestInput ? (input as RequestInit) : init
289288
)
290289
} catch (err) {
@@ -329,7 +328,6 @@ export function patchFetch({
329328
}
330329
}
331330

332-
const fetchUrl = url?.toString() ?? ''
333331
const fetchIdx = staticGenerationStore.nextFetchId ?? 1
334332
staticGenerationStore.nextFetchId = fetchIdx + 1
335333

test/e2e/app-dir/app-static/app-static.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -974,6 +974,12 @@ createNextDescribe(
974974

975975
return 'success'
976976
}, 'success')
977+
978+
if (!isNextDeploy) {
979+
expect(next.cliOutput).toContain(
980+
'Warning: fetch for https://next-data-api-endpoint.vercel.app/api/random?d4 on /force-cache specified "cache: force-cache" and "revalidate: 3", only one should be specified.'
981+
)
982+
}
977983
})
978984

979985
it('should cache correctly for cache: no-store', async () => {
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
'use client'
2+
import Link from 'next/link'
3+
4+
export default function ErrorComponent() {
5+
return (
6+
<>
7+
<h1 id="error-component">Error Happened!</h1>
8+
<Link href="/result" id="to-result">
9+
To Result
10+
</Link>
11+
</>
12+
)
13+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export default function Root({ children }: { children: React.ReactNode }) {
2+
return (
3+
<html>
4+
<body>{children}</body>
5+
</html>
6+
)
7+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import Link from 'next/link'
2+
3+
export default function NotFound() {
4+
return (
5+
<>
6+
<h1 id="not-found-component">Not Found!</h1>
7+
<Link href="/result" id="to-result">
8+
To Result
9+
</Link>
10+
</>
11+
)
12+
}
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 <h1 id="homepage">Home</h1>
3+
}
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 <h1 id="result-page">Result Page!</h1>
3+
}

0 commit comments

Comments
 (0)