@@ -20,6 +20,7 @@ import {
2020 createMutableActionQueue ,
2121} from './components/app-router-instance'
2222import AppRouter from './components/app-router'
23+ import DefaultGlobalError from './components/builtin/global-error'
2324import type { InitialRSCPayload } from '../shared/lib/app-router-types'
2425import { createInitialRouterState } from './components/router-reducer/create-initial-router-state'
2526import { MissingSlotContext } from '../shared/lib/app-router-context.shared-runtime'
@@ -63,12 +64,17 @@ type OfflineNavigationCacheMissReason =
6364 | 'unsupported-request-kind'
6465 | 'read-error'
6566
67+ type OfflineNavigationFallbackRequestKind =
68+ | 'client-resume'
69+ | 'initial-load'
70+ | 'router-cache'
71+
6672type OfflineNavigationFallbackDiagnostic =
6773 | {
6874 type : 'cache-hit'
6975 url : string
7076 buildId : string | undefined
71- requestKind : OfflineNavigationFallbackResponse [ 'requestKind' ]
77+ requestKind : OfflineNavigationFallbackRequestKind
7278 }
7379 | {
7480 type : 'cache-miss'
@@ -89,6 +95,12 @@ type OfflineNavigationFallbackDiagnostic =
8995 skipped : number
9096 }
9197 }
98+ | {
99+ type : 'router-cache-reconstruction-miss'
100+ url : string
101+ buildId : string | undefined
102+ reason : string
103+ }
92104
93105declare global {
94106 interface Window {
@@ -109,7 +121,7 @@ function reportOfflineNavigationFallbackDiagnostic(
109121}
110122
111123function showOfflineNavigationCacheHit (
112- requestKind : OfflineNavigationFallbackResponse [ 'requestKind' ] ,
124+ requestKind : OfflineNavigationFallbackRequestKind ,
113125 buildId : string | undefined
114126) : void {
115127 document . documentElement . setAttribute (
@@ -157,23 +169,39 @@ function showOfflineNavigationCacheMiss(
157169 } )
158170}
159171
160- function neverResolveOfflineNavigationResponse ( ) : Promise < Response > {
161- return new Promise < Response > ( ( ) => { } )
172+ function neverResolveInitialRSCPayload ( ) : Promise < InitialRSCPayload > {
173+ return new Promise < InitialRSCPayload > ( ( ) => { } )
162174}
163175
164176type OfflineNavigationFallbackResponse = {
165177 requestKind : 'client-resume' | 'initial-load'
166178 response : Response
167179}
168180
169- function createOfflineNavigationFallbackResponse ( ) :
170- | Promise < OfflineNavigationFallbackResponse >
181+ type OfflineNavigationFallbackBootstrap =
182+ | {
183+ kind : 'rsc-response'
184+ response : OfflineNavigationFallbackResponse
185+ buildId : string | undefined
186+ }
187+ | {
188+ kind : 'router-cache'
189+ initialRSCPayload : InitialRSCPayload
190+ buildId : string | undefined
191+ }
192+ | {
193+ kind : 'cache-miss'
194+ buildId : string | undefined
195+ }
196+
197+ function createOfflineNavigationFallbackBootstrap ( ) :
198+ | Promise < OfflineNavigationFallbackBootstrap >
171199 | undefined {
172200 if ( ! isOfflineNavigationFallbackDocument ( ) ) {
173201 return undefined
174202 }
175203
176- return ( async ( ) : Promise < OfflineNavigationFallbackResponse > => {
204+ return ( async ( ) : Promise < OfflineNavigationFallbackBootstrap > => {
177205 const {
178206 createOfflineNavigationRSCResponse,
179207 isOfflineNavigationRSCResponsePayload,
@@ -191,13 +219,54 @@ function createOfflineNavigationFallbackResponse():
191219 const payload = entry ?. payload
192220
193221 if ( ! isOfflineNavigationRSCResponsePayload ( payload ) ) {
222+ if ( payload === undefined ) {
223+ const {
224+ createOfflineNavigationInitialRSCPayloadFromRouterCache,
225+ hydrateOfflineNavigationRouterCache,
226+ } =
227+ require ( './components/segment-cache/cache' ) as typeof import ( './components/segment-cache/cache' )
228+
229+ const hydrationResult = await hydrateOfflineNavigationRouterCache ( {
230+ buildId,
231+ } )
232+ reportOfflineNavigationFallbackDiagnostic ( {
233+ type : 'router-cache-hydration' ,
234+ url : location . href ,
235+ buildId,
236+ routes : hydrationResult . routes ,
237+ segments : hydrationResult . segments ,
238+ } )
239+
240+ const reconstruction =
241+ createOfflineNavigationInitialRSCPayloadFromRouterCache ( {
242+ buildId,
243+ globalErrorState : [ DefaultGlobalError , undefined ] ,
244+ now : Date . now ( ) ,
245+ url : location . href ,
246+ } )
247+ if ( reconstruction . status === 'fulfilled' ) {
248+ showOfflineNavigationCacheHit ( 'router-cache' , buildId )
249+ return {
250+ kind : 'router-cache' ,
251+ initialRSCPayload : reconstruction . initialRSCPayload ,
252+ buildId,
253+ }
254+ }
255+ reportOfflineNavigationFallbackDiagnostic ( {
256+ type : 'router-cache-reconstruction-miss' ,
257+ url : location . href ,
258+ buildId,
259+ reason : reconstruction . reason ,
260+ } )
261+ }
262+
194263 showOfflineNavigationCacheMiss (
195264 payload === undefined ? 'missing-entry' : 'invalid-payload' ,
196265 buildId
197266 )
198267 return {
199- requestKind : 'client-resume ' ,
200- response : await neverResolveOfflineNavigationResponse ( ) ,
268+ kind : 'cache-miss ' ,
269+ buildId ,
201270 }
202271 }
203272
@@ -207,8 +276,8 @@ function createOfflineNavigationFallbackResponse():
207276 ) {
208277 showOfflineNavigationCacheMiss ( 'unsupported-request-kind' , buildId )
209278 return {
210- requestKind : 'client-resume ' ,
211- response : await neverResolveOfflineNavigationResponse ( ) ,
279+ kind : 'cache-miss ' ,
280+ buildId ,
212281 }
213282 }
214283
@@ -217,29 +286,37 @@ function createOfflineNavigationFallbackResponse():
217286
218287 showOfflineNavigationCacheHit ( requestKind , buildId )
219288 return {
220- requestKind,
221- response : createOfflineNavigationRSCResponse ( payload ) ,
289+ kind : 'rsc-response' ,
290+ buildId,
291+ response : {
292+ requestKind,
293+ response : createOfflineNavigationRSCResponse ( payload ) ,
294+ } ,
222295 }
223- } ) ( ) . catch ( async ( ) : Promise < OfflineNavigationFallbackResponse > => {
296+ } ) ( ) . catch ( ( ) : OfflineNavigationFallbackBootstrap => {
224297 showOfflineNavigationCacheMiss ( 'read-error' , undefined )
225298 return {
226- requestKind : 'client-resume ' ,
227- response : await neverResolveOfflineNavigationResponse ( ) ,
299+ kind : 'cache-miss ' ,
300+ buildId : undefined ,
228301 }
229302 } )
230303}
231304
232- const offlineNavigationFallbackResponse =
233- createOfflineNavigationFallbackResponse ( )
234- const offlineNavigationClientResumeFetch =
235- offlineNavigationFallbackResponse ?. then ( ( { response } ) => response )
305+ const offlineNavigationFallbackBootstrap =
306+ createOfflineNavigationFallbackBootstrap ( )
307+
308+ if ( process . env . __NEXT_USE_OFFLINE && offlineNavigationFallbackBootstrap ) {
309+ const { notifyOffline } =
310+ require ( './components/offline' ) as typeof import ( './components/offline' )
311+ notifyOffline ( )
312+ }
236313
237314const hasClientResumeShell =
238315 // @ts -expect-error
239316 Boolean ( window . __NEXT_CLIENT_RESUME )
240317const hasLockedStaticShell =
241318 Boolean ( instantTestStaticFetch ) ||
242- Boolean ( offlineNavigationClientResumeFetch ) ||
319+ Boolean ( offlineNavigationFallbackBootstrap ) ||
243320 hasClientResumeShell
244321
245322const encoder = new TextEncoder ( )
410487if (
411488 process . env . __NEXT_CACHE_COMPONENTS &&
412489 process . env . __NEXT_EXPERIMENTAL_CACHED_NAVIGATIONS &&
413- ! offlineNavigationClientResumeFetch
490+ ! offlineNavigationFallbackBootstrap
414491) {
415492 const [ forReact , forCache ] = readable . tee ( )
416493 readable = forReact
@@ -454,25 +531,39 @@ if (instantTestStaticFetch) {
454531 initialRSCPayload
455532 )
456533 } )
457- } else if ( offlineNavigationClientResumeFetch ) {
458- initialServerResponse = Promise . resolve (
459- createFromFetch < InitialRSCPayload > ( offlineNavigationClientResumeFetch , {
460- callServer,
461- findSourceMapURL,
462- debugChannel,
463- unstable_allowPartialStream : true ,
464- } )
465- ) . then ( async ( fallbackInitialRSCPayload ) => {
466- const fallbackResponse = await offlineNavigationFallbackResponse !
467- if ( fallbackResponse . requestKind === 'initial-load' ) {
468- return fallbackInitialRSCPayload
469- }
534+ } else if ( offlineNavigationFallbackBootstrap ) {
535+ initialServerResponse = offlineNavigationFallbackBootstrap . then (
536+ async ( bootstrap ) => {
537+ if ( bootstrap . kind === 'cache-miss' ) {
538+ return await neverResolveInitialRSCPayload ( )
539+ }
470540
471- return createInitialRSCPayloadFromFallbackPrerender (
472- fallbackResponse . response ,
473- fallbackInitialRSCPayload
474- )
475- } )
541+ if ( bootstrap . kind === 'router-cache' ) {
542+ return bootstrap . initialRSCPayload
543+ }
544+
545+ const fallbackResponse = bootstrap . response
546+ const fallbackInitialRSCPayload =
547+ await createFromFetch < InitialRSCPayload > (
548+ Promise . resolve ( fallbackResponse . response ) ,
549+ {
550+ callServer,
551+ findSourceMapURL,
552+ debugChannel,
553+ unstable_allowPartialStream : true ,
554+ }
555+ )
556+
557+ if ( fallbackResponse . requestKind === 'initial-load' ) {
558+ return fallbackInitialRSCPayload
559+ }
560+
561+ return createInitialRSCPayloadFromFallbackPrerender (
562+ fallbackResponse . response ,
563+ fallbackInitialRSCPayload
564+ )
565+ }
566+ )
476567} else if (
477568 // @ts -expect-error
478569 window . __NEXT_CLIENT_RESUME
@@ -598,7 +689,7 @@ export async function hydrate(
598689 if ( process . env . __NEXT_USE_OFFLINE ) {
599690 const { notifyOffline } =
600691 require ( './components/offline' ) as typeof import ( './components/offline' )
601- if ( offlineNavigationClientResumeFetch ) {
692+ if ( offlineNavigationFallbackBootstrap ) {
602693 notifyOffline ( )
603694 }
604695 }
@@ -624,19 +715,22 @@ export async function hydrate(
624715 if (
625716 process . env . __NEXT_OFFLINE_NAVIGATIONS &&
626717 ! process . env . __NEXT_DEV_SERVER &&
627- offlineNavigationClientResumeFetch
718+ offlineNavigationFallbackBootstrap
628719 ) {
629720 try {
630- const { hydrateOfflineNavigationRouterCache } =
631- require ( './components/segment-cache/cache' ) as typeof import ( './components/segment-cache/cache' )
632- const result = await hydrateOfflineNavigationRouterCache ( )
633- reportOfflineNavigationFallbackDiagnostic ( {
634- type : 'router-cache-hydration' ,
635- url : location . href ,
636- buildId,
637- routes : result . routes ,
638- segments : result . segments ,
639- } )
721+ const bootstrap = await offlineNavigationFallbackBootstrap
722+ if ( bootstrap . kind === 'rsc-response' ) {
723+ const { hydrateOfflineNavigationRouterCache } =
724+ require ( './components/segment-cache/cache' ) as typeof import ( './components/segment-cache/cache' )
725+ const result = await hydrateOfflineNavigationRouterCache ( { buildId } )
726+ reportOfflineNavigationFallbackDiagnostic ( {
727+ type : 'router-cache-hydration' ,
728+ url : location . href ,
729+ buildId,
730+ routes : result . routes ,
731+ segments : result . segments ,
732+ } )
733+ }
640734 } catch {
641735 // The exact URL fallback already booted. Router cache hydration should
642736 // only improve later navigations, never block the current render.
0 commit comments