Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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>
)
}
96 changes: 53 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,26 @@ 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 ? 'static' : 'dynamic'}.`,
label: 'Route',
value:
state.staticIndicator === 'static' ? 'Static' : 'Dynamic',
onClick: () => setPanel('route-type'),
attributes: {
'data-nextjs-route-type': state.staticIndicator
? 'static'
: 'dynamic',
},
},
!!process.env.TURBOPACK
? {
title: 'Turbopack is enabled.',
Expand Down Expand Up @@ -166,39 +176,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' : '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