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
2 changes: 1 addition & 1 deletion packages/next/src/client/app-index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ export function hydrate(
const { createWebSocket } =
require('./dev/hot-reloader/app/web-socket') as typeof import('./dev/hot-reloader/app/web-socket')

staticIndicatorState = { pathname: null, appIsrManifest: {} }
staticIndicatorState = { pathname: null, appIsrManifest: null }
webSocket = createWebSocket(assetPrefix, staticIndicatorState)
}

Expand Down
38 changes: 21 additions & 17 deletions packages/next/src/client/dev/hot-reloader/app/hot-reloader-app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import { getOrCreateDebugChannelReadableWriterPair } from '../../debug-channel'

export interface StaticIndicatorState {
pathname: string | null
appIsrManifest: Record<string, true>
appIsrManifest: Record<string, boolean> | null
}

let mostRecentCompilationHash: any = null
Expand Down Expand Up @@ -261,18 +261,18 @@ export function processMessage(
if (process.env.__NEXT_DEV_INDICATOR) {
staticIndicatorState.appIsrManifest = message.data

// handle initial status on receiving manifest
// navigation is handled in useEffect for pathname changes
// as we'll receive the updated manifest before usePathname
// triggers for new value
if (
staticIndicatorState.pathname &&
staticIndicatorState.pathname in message.data
) {
dispatcher.onStaticIndicator(true)
} else {
dispatcher.onStaticIndicator(false)
}
// Handle the initial static indicator status on receiving the ISR
// manifest. Navigation is handled in an effect inside HotReload for
// pathname changes as we'll receive the updated manifest before
// usePathname triggers for a new value.

const isStatic = staticIndicatorState.pathname
? message.data[staticIndicatorState.pathname]
: undefined

dispatcher.onStaticIndicator(
isStatic === undefined ? 'pending' : isStatic ? 'static' : 'dynamic'
)
}
break
}
Expand Down Expand Up @@ -542,10 +542,14 @@ export default function HotReload({

staticIndicatorState.pathname = pathname

if (pathname && pathname in staticIndicatorState.appIsrManifest) {
dispatcher.onStaticIndicator(true)
} else {
dispatcher.onStaticIndicator(false)
if (staticIndicatorState.appIsrManifest) {
const isStatic = pathname
? staticIndicatorState.appIsrManifest[pathname]
: undefined

dispatcher.onStaticIndicator(
isStatic === undefined ? 'pending' : isStatic ? 'static' : 'dynamic'
)
}
}, [pathname, staticIndicatorState])
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -265,10 +265,10 @@ export function handleStaticIndicator() {
appComponent?.getInitialProps !== appComponent?.origGetInitialProps

const isPageStatic =
window.location.pathname in isrManifest ||
isrManifest[window.location.pathname] ||
(!isDynamicPage && !hasAppGetInitialProps)

dispatcher.onStaticIndicator(isPageStatic)
dispatcher.onStaticIndicator(isPageStatic ? 'static' : 'dynamic')
}
}

Expand Down
13 changes: 9 additions & 4 deletions packages/next/src/next-devtools/dev-overlay.browser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export interface Dispatcher {
onDebugInfo(debugInfo: DebugInfo): void
onBeforeRefresh(): void
onRefresh(): void
onStaticIndicator(status: boolean): void
onStaticIndicator(status: 'pending' | 'static' | 'dynamic' | 'disabled'): void
onDevIndicator(devIndicator: DevIndicatorServerState): void
onDevToolsConfig(config: DevToolsConfig): void
onUnhandledError(reason: Error): void
Expand Down Expand Up @@ -147,9 +147,14 @@ export const dispatcher: Dispatcher = {
dispatch({ type: ACTION_VERSION_INFO, versionInfo })
}
),
onStaticIndicator: createQueuable((dispatch: Dispatch, status: boolean) => {
dispatch({ type: ACTION_STATIC_INDICATOR, staticIndicator: status })
}),
onStaticIndicator: createQueuable(
(
dispatch: Dispatch,
status: 'pending' | 'static' | 'dynamic' | 'disabled'
) => {
dispatch({ type: ACTION_STATIC_INDICATOR, staticIndicator: status })
}
),
onDebugInfo: createQueuable((dispatch: Dispatch, debugInfo: DebugInfo) => {
dispatch({ type: ACTION_DEBUG_INFO, debugInfo })
}),
Expand Down
31 changes: 31 additions & 0 deletions packages/next/src/next-devtools/dev-overlay/icons/loading-icon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
export function LoadingIcon() {
return (
<svg
width="20px"
height="20px"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<circle
cx="10"
cy="10"
r="7"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeDasharray="32 12"
opacity="0.8"
>
<animateTransform
attributeName="transform"
type="rotate"
from="0 10 10"
to="360 10 10"
dur="1s"
repeatCount="indefinite"
/>
</circle>
</svg>
)
}
94 changes: 51 additions & 43 deletions packages/next/src/next-devtools/dev-overlay/menu/panel-router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
ACTION_ERROR_OVERLAY_OPEN,
} from '../shared'
import GearIcon from '../icons/gear-icon'
import { LoadingIcon } from '../icons/loading-icon'
import { UserPreferencesBody } from '../components/errors/dev-tools-indicator/dev-tools-info/user-preferences'
import { useShortcuts } from '../hooks/use-shortcuts'
import { useUpdateAllPanelPositions } from '../components/devtools-indicator/devtools-indicator'
Expand Down Expand Up @@ -60,17 +61,24 @@ const MenuPanel = () => {
}
},
},
{
title: `Current route is ${state.staticIndicator ? 'static' : 'dynamic'}.`,
label: 'Route',
value: state.staticIndicator ? 'Static' : 'Dynamic',
onClick: () => setPanel('route-type'),
attributes: {
'data-nextjs-route-type': state.staticIndicator
? 'static'
: 'dynamic',
},
},
state.staticIndicator === 'disabled'
? undefined
: state.staticIndicator === 'pending'
? {
title: 'Loading...',
label: 'Route',
value: <LoadingIcon />,
}
: {
title: `Current route is ${state.staticIndicator}.`,
label: 'Route',
value:
state.staticIndicator === 'static' ? 'Static' : 'Dynamic',
onClick: () => setPanel('route-type'),
attributes: {
'data-nextjs-route-type': state.staticIndicator,
},
},
!!process.env.TURBOPACK
? {
title: 'Turbopack is enabled.',
Expand Down Expand Up @@ -166,39 +174,39 @@ export const PanelRouter = () => {
</DynamicPanel>
</PanelRoute>

<PanelRoute name="route-type">
<DynamicPanel
key={state.staticIndicator ? 'static' : 'dynamic'}
sharePanelSizeGlobally={false}
sizeConfig={{
kind: 'fixed',
height: state.staticIndicator
? 300 / state.scale
: 325 / state.scale,
width: 400 / state.scale,
}}
closeOnClickOutside
header={
<DevToolsHeader
title={`${state.staticIndicator ? 'Static' : 'Dynamic'} Route`}
/>
}
>
<div className="panel-content">
<RouteInfoBody
routerType={state.routerType}
isStaticRoute={state.staticIndicator}
/>
<InfoFooter
href={
learnMoreLink[state.routerType][
state.staticIndicator ? 'static' : 'dynamic'
]
{state.staticIndicator !== 'disabled' &&
state.staticIndicator !== 'pending' && (
<PanelRoute name="route-type">
<DynamicPanel
key={state.staticIndicator}
sharePanelSizeGlobally={false}
sizeConfig={{
kind: 'fixed',
height:
state.staticIndicator === 'static'
? 300 / state.scale
: 325 / state.scale,
width: 400 / state.scale,
}}
closeOnClickOutside
header={
<DevToolsHeader
title={`${state.staticIndicator === 'static' ? 'Static' : 'Dynamic'} Route`}
/>
}
/>
</div>
</DynamicPanel>
</PanelRoute>
>
<div className="panel-content">
<RouteInfoBody
routerType={state.routerType}
isStaticRoute={state.staticIndicator === 'static'}
/>
<InfoFooter
href={learnMoreLink[state.routerType][state.staticIndicator]}
/>
</div>
</DynamicPanel>
</PanelRoute>
)}

{isAppRouter && (
<PanelRoute name="segment-explorer">
Expand Down
6 changes: 3 additions & 3 deletions packages/next/src/next-devtools/dev-overlay/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export interface OverlayState {
readonly notFound: boolean
readonly buildingIndicator: boolean
readonly renderingIndicator: boolean
readonly staticIndicator: boolean
readonly staticIndicator: 'pending' | 'static' | 'dynamic' | 'disabled'
readonly showIndicator: boolean
readonly disableDevIndicator: boolean
readonly debugInfo: DebugInfo
Expand Down Expand Up @@ -112,7 +112,7 @@ export const ACTION_DEVTOOL_UPDATE_ROUTE_STATE =

interface StaticIndicatorAction {
type: typeof ACTION_STATIC_INDICATOR
staticIndicator: boolean
staticIndicator: 'pending' | 'static' | 'dynamic' | 'disabled'
}

interface BuildOkAction {
Expand Down Expand Up @@ -263,7 +263,7 @@ export const INITIAL_OVERLAY_STATE: Omit<
errors: [],
notFound: false,
renderingIndicator: false,
staticIndicator: false,
staticIndicator: 'disabled',
/*
This is set to `true` when we can reliably know
whether the indicator is in disabled state or not.
Expand Down
29 changes: 19 additions & 10 deletions packages/next/src/server/app-render/app-render.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1502,6 +1502,7 @@ async function renderToHTMLOrFlightImpl(
serverActions,
assetPrefix = '',
enableTainting,
experimental,
} = renderOpts

// We need to expose the bundled `require` API globally for
Expand Down Expand Up @@ -1566,10 +1567,18 @@ async function renderToHTMLOrFlightImpl(
globalThis.__next_chunk_load__ = __next_chunk_load__
}

if (process.env.NODE_ENV === 'development') {
// reset isr status at start of request
if (
process.env.NODE_ENV === 'development' &&
renderOpts.setIsrStatus &&
!experimental.cacheComponents
) {
// Reset the ISR status at start of request.
const { pathname } = new URL(req.url || '/', 'http://n')
renderOpts.setIsrStatus?.(pathname, false)
renderOpts.setIsrStatus(
pathname,
// Only pages using the Node runtime can use ISR, Edge is always dynamic.
process.env.NEXT_RUNTIME === 'edge' ? false : undefined
)
}

if (
Expand Down Expand Up @@ -1844,19 +1853,19 @@ async function renderToHTMLOrFlightImpl(
if (
process.env.NODE_ENV === 'development' &&
renderOpts.setIsrStatus &&
!experimental.cacheComponents &&
// Only pages using the Node runtime can use ISR, so we only need to
// update the status for those.
// The type check here ensures that `req` is correctly typed, and the
// environment variable check provides dead code elimination.
process.env.NEXT_RUNTIME !== 'edge' &&
isNodeNextRequest(req) &&
!isDevWarmupRequest
isNodeNextRequest(req)
) {
const setIsrStatus = renderOpts.setIsrStatus
req.originalRequest.on('end', () => {
if (!requestStore.usedDynamic && !workStore.forceDynamic) {
// only node can be ISR so we only need to update the status here
const { pathname } = new URL(req.url || '/', 'http://n')
setIsrStatus(pathname, true)
}
const { pathname } = new URL(req.url || '/', 'http://n')
const isStatic = !requestStore.usedDynamic && !workStore.forceDynamic
setIsrStatus(pathname, isStatic)
})
}

Expand Down
2 changes: 1 addition & 1 deletion packages/next/src/server/app-render/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export interface RenderOptsPartial {
}
isOnDemandRevalidate?: boolean
isPossibleServerAction?: boolean
setIsrStatus?: (key: string, value: boolean) => void
setIsrStatus?: (key: string, value: boolean | undefined) => void
setReactDebugChannel?: (
debugChannel: { readable: ReadableStream<Uint8Array> },
htmlRequestId: string,
Expand Down
Loading
Loading