Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 0 additions & 67 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/next/src/cli/internal/upload-trace.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import path from 'path'
import fs from 'fs/promises'

const UPLOAD_TRACE_URL = 'https://api.nextjs.org/api/upload-trace'
const UPLOAD_TRACE_URL = 'https://nextjs.org/api/upload-trace'

// V8 CPU profiles are JSON objects starting with {"nodes":
const CPUPROFILE_HEADER = Buffer.from('{"nodes":')
Expand Down
22 changes: 13 additions & 9 deletions packages/next/src/client/components/catch-error.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,15 @@ type CatchErrorProps<P extends UserProps> = {
}

type CatchErrorState = {
error: Error | null
error: null | { thrownValue: unknown }
previousPathname: string | null
}

// This is forked from error-boundary.
// TODO: Extend it instead of forking to easily sync the behavior?
class CatchError<P extends UserProps> extends React.Component<
CatchErrorProps<P>,
{ error: Error | null; previousPathname: string | null }
CatchErrorState
> {
declare context: AppRouterInstance | null
static contextType = AppRouterContext
Expand All @@ -54,14 +54,16 @@ class CatchError<P extends UserProps> extends React.Component<
}
}

static getDerivedStateFromError(error: Error) {
if (isNextRouterError(error)) {
static getDerivedStateFromError(
thrownValue: unknown
): Partial<CatchErrorState> {
if (isNextRouterError(thrownValue)) {
// Re-throw if an expected internal Next.js router error occurs
// this means it should be handled by a different boundary (such as a NotFound boundary in a parent segment)
throw error
throw thrownValue
}

return { error }
return { error: { thrownValue } }
}

static getDerivedStateFromProps(
Expand All @@ -75,7 +77,7 @@ class CatchError<P extends UserProps> extends React.Component<
// the error boundary and instead should fallback
// to a hard navigation to attempt recovering
if (process.env.__NEXT_APP_NAV_FAIL_HANDLING) {
if (error && handleHardNavError(error)) {
if (error && handleHardNavError(error.thrownValue)) {
// clear error so we don't render anything
return {
error: null,
Expand Down Expand Up @@ -124,13 +126,15 @@ class CatchError<P extends UserProps> extends React.Component<
//When it's bot request, segment level error boundary will keep rendering the children,
// the final error will be caught by the root error boundary and determine wether need to apply graceful degrade.
if (this.state.error && !isBotUserAgent) {
handleISRError({ error: this.state.error })
const thrownValue = this.state.error.thrownValue
handleISRError({ error: thrownValue })

return (
<this.props.fallback
props={this.props.props}
errorInfo={{
error: this.state.error,
// TODO(NAR-804): Docs say this is an Error object, but we don't guarantee that
error: thrownValue,
reset: this.reset,
unstable_retry: this.unstable_retry,
}}
Expand Down
22 changes: 13 additions & 9 deletions packages/next/src/client/components/error-boundary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const isBotUserAgent =
typeof window !== 'undefined' && isBot(window.navigator.userAgent)

export type ErrorInfo = {
error: Error
error: unknown
reset: () => void
unstable_retry: () => void
}
Expand All @@ -35,7 +35,7 @@ interface ErrorBoundaryHandlerProps extends ErrorBoundaryProps {
}

interface ErrorBoundaryHandlerState {
error: Error | null
error: null | { thrownValue: unknown }
previousPathname: string | null
}

Expand All @@ -54,14 +54,16 @@ export class ErrorBoundaryHandler extends React.Component<
}
}

static getDerivedStateFromError(error: Error) {
if (isNextRouterError(error)) {
static getDerivedStateFromError(
thrownValue: unknown
): Partial<ErrorBoundaryHandlerState> {
if (isNextRouterError(thrownValue)) {
// Re-throw if an expected internal Next.js router error occurs
// this means it should be handled by a different boundary (such as a NotFound boundary in a parent segment)
throw error
throw thrownValue
}

return { error }
return { error: { thrownValue } }
}

static getDerivedStateFromProps(
Expand All @@ -75,7 +77,7 @@ export class ErrorBoundaryHandler extends React.Component<
// the error boundary and instead should fallback
// to a hard navigation to attempt recovering
if (process.env.__NEXT_APP_NAV_FAIL_HANDLING) {
if (error && handleHardNavError(error)) {
if (error && handleHardNavError(error.thrownValue)) {
// clear error so we don't render anything
return {
error: null,
Expand Down Expand Up @@ -118,14 +120,16 @@ export class ErrorBoundaryHandler extends React.Component<
//When it's bot request, segment level error boundary will keep rendering the children,
// the final error will be caught by the root error boundary and determine wether need to apply graceful degrade.
if (this.state.error && !isBotUserAgent) {
handleISRError({ error: this.state.error })
const thrownValue = this.state.error.thrownValue
handleISRError({ error: thrownValue })

return (
<>
{this.props.errorStyles}
{this.props.errorScripts}
<this.props.errorComponent
error={this.state.error}
// TODO(NAR-804): Docs say this is an Error object, but we don't guarantee that
error={thrownValue}
reset={this.reset}
unstable_retry={this.unstable_retry}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ class HTTPAccessFallbackErrorBoundary extends React.Component<
}
}

static getDerivedStateFromError(error: any) {
static getDerivedStateFromError(error: unknown) {
if (isHTTPAccessFallbackError(error)) {
const httpStatus = getAccessFallbackHTTPStatus(error)
return {
Expand Down
1 change: 0 additions & 1 deletion packages/next/src/client/components/nav-failure-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { createHrefFromUrl } from './router-reducer/create-href-from-url'

export function handleHardNavError(error: unknown): boolean {
if (
error &&
typeof window !== 'undefined' &&
window.next.__pendingUrl &&
createHrefFromUrl(new URL(window.location.href)) !==
Expand Down
2 changes: 1 addition & 1 deletion packages/next/src/client/components/redirect-boundary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export class RedirectErrorBoundary extends React.Component<
this.state = { redirect: null, redirectType: null }
}

static getDerivedStateFromError(error: any) {
static getDerivedStateFromError(error: unknown) {
if (isRedirectError(error)) {
const url = getURLFromRedirectError(error)
const redirectType = getRedirectTypeFromError(error)
Expand Down
62 changes: 40 additions & 22 deletions packages/next/src/client/image-component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
import React, {
useRef,
useEffect,
useCallback,
useContext,
useMemo,
useState,
forwardRef,
use,
useLayoutEffect,
} from 'react'
import ReactDOM from 'react-dom'
import Head from '../shared/lib/head'
Expand Down Expand Up @@ -44,7 +44,7 @@ export type { ImageLoaderProps }
export type ImageLoader = (p: ImageLoaderProps) => string

type ImgElementWithDataProp = HTMLImageElement & {
'data-loaded-src': string | undefined
'data-loaded-src'?: string | undefined
}

type ImageElementProps = ImgProps & {
Expand Down Expand Up @@ -180,6 +180,15 @@ function getDynamicProps(
return { fetchpriority: fetchPriority }
}

/**
* A version of useLayoutEffect that doesn't warn during SSR.
* TODO: Just useLayoutEffect once support for React 18 is dropped.
* Do not rename this to "isomorphic layout effect". There is no such thing as
* an isomorphic Layout Effect since there is no Layout on the server
*/
const useNonWarningLayoutEffect =
typeof window === 'undefined' ? useEffect : useLayoutEffect

const ImageElement = forwardRef<HTMLImageElement | null, ImageElementProps>(
(
{
Expand Down Expand Up @@ -207,18 +216,25 @@ const ImageElement = forwardRef<HTMLImageElement | null, ImageElementProps>(
},
forwardedRef
) => {
const ownRef = useCallback(
(img: ImgElementWithDataProp | null) => {
if (!img) {
return
}
const didInsertRef = useRef(false)
const insertedImgRef = useRef<HTMLImageElement>(null)

useNonWarningLayoutEffect(() => {
const { current: didInsert } = didInsertRef
const { current: img } = insertedImgRef

if (!didInsert && img !== null) {
// Replay events from during hydration that React doesn't replay.
if (onError) {
// If the image has an error before react hydrates, then the error is lost.
// The workaround is to wait until the image is mounted which is after hydration,
// then we set the src again to trigger the error handler (if there was an error).
// This doesn't just trigger the error handler but retries the whole request.
// TODO: Consider dispatching a synthetic event instead.
// eslint-disable-next-line no-self-assign
img.src = img.src
}

if (process.env.NODE_ENV !== 'production') {
if (!src) {
console.error(`Image is missing required "src" property:`, img)
Expand All @@ -240,22 +256,24 @@ const ImageElement = forwardRef<HTMLImageElement | null, ImageElementProps>(
sizesInput
)
}
},
[
src,
placeholder,
onLoadRef,
onLoadingCompleteRef,
setBlurComplete,
onError,
unoptimized,
sizesInput,
]
)
didInsertRef.current = true
}
}, [
src,
placeholder,
onLoadRef,
onLoadingCompleteRef,
onError,
unoptimized,
sizesInput,
])

const ref = useMergedRef(forwardedRef, ownRef)
const ref = useMergedRef(forwardedRef, insertedImgRef)

return (
// If you move this element creation, also move the Layout Effect above
// reading from the ref. Otherwise we might run the Layout Effect when
// the current value isn't set to the HTMLImageElement instance.
<img
{...rest}
{...getDynamicProps(fetchPriority)}
Expand All @@ -280,9 +298,9 @@ const ImageElement = forwardRef<HTMLImageElement | null, ImageElementProps>(
src={src}
ref={ref}
onLoad={(event) => {
const img = event.currentTarget as ImgElementWithDataProp
const currentImage = event.currentTarget
handleLoading(
img,
currentImage,
placeholder,
onLoadRef,
onLoadingCompleteRef,
Expand Down
Loading
Loading