@thomas.f/prnext 0.1.17-alpha → 0.1.18-alpha

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/README.md +26 -869
  2. package/build/actions.mjs +1 -1
  3. package/build/analyze.mjs +180 -24
  4. package/build/app-config.mjs +13 -0
  5. package/build/app-static.mjs +34 -12
  6. package/build/app-styles.mjs +79 -0
  7. package/build/app.mjs +81 -26
  8. package/build/bundle-runtime.mjs +22 -9
  9. package/build/cache-components.mjs +1 -1
  10. package/build/cache-handlers.mjs +1 -1
  11. package/build/compact-css.mjs +53 -0
  12. package/build/compilation-cache.mjs +269 -0
  13. package/build/compilation-store.mjs +142 -0
  14. package/build/compiler.mjs +31 -8
  15. package/build/config.mjs +30 -1
  16. package/build/create.mjs +239 -0
  17. package/build/css-modules.mjs +56 -12
  18. package/build/deployment-assets.mjs +89 -0
  19. package/build/deployment-config.mjs +18 -0
  20. package/build/dev-css-update.mjs +4 -2
  21. package/build/dev-dependencies.mjs +1 -1
  22. package/build/dev-discovery.mjs +3 -3
  23. package/build/dev.mjs +15 -8
  24. package/build/dynamic.mjs +1 -1
  25. package/build/edge.mjs +3 -3
  26. package/build/fonts.mjs +1 -1
  27. package/build/framework-imports.mjs +15 -5
  28. package/build/html-only-config.mjs +53 -0
  29. package/build/html-only-graph.mjs +67 -0
  30. package/build/index.mjs +59 -26
  31. package/build/instrumentation.mjs +30 -0
  32. package/build/metadata-routes.mjs +1 -1
  33. package/build/migration-audit.mjs +1 -1
  34. package/build/native-type-config.mjs +37 -0
  35. package/build/persistent-cache.mjs +18 -3
  36. package/build/precompress.mjs +1 -1
  37. package/build/prerender-cache.mjs +70 -0
  38. package/build/prerender-config.mjs +29 -0
  39. package/build/prerender-pages.mjs +19 -9
  40. package/build/prerender-pool.mjs +165 -0
  41. package/build/prerender-process-worker.mjs +58 -0
  42. package/build/prerender-worker.mjs +5 -5
  43. package/build/production-builder.mjs +58 -9
  44. package/build/react-compiler-report.mjs +225 -0
  45. package/build/react-compiler.mjs +139 -0
  46. package/build/runtime-contract.mjs +2 -2
  47. package/build/static-export.mjs +4 -4
  48. package/build/styles.mjs +6 -4
  49. package/build/transform.mjs +1 -1
  50. package/build/typed-routes.mjs +4 -0
  51. package/build/typescript-dev.mjs +35 -8
  52. package/build/typescript-native-worker.mjs +55 -0
  53. package/build/typescript-native.mjs +18 -0
  54. package/build/typescript-toolchain.mjs +19 -0
  55. package/build/typescript.mjs +6 -4
  56. package/build/webpack-graph.mjs +6 -3
  57. package/build/worker-memory.mjs +9 -0
  58. package/cli.mjs +60 -4
  59. package/compat/activity-cache.cjs +26 -0
  60. package/compat/after.cjs +6 -4
  61. package/compat/app-layout-context.cjs +52 -2
  62. package/compat/base64.cjs +10 -0
  63. package/compat/cache-handlers.cjs +12 -2
  64. package/compat/cache-wire.cjs +28 -0
  65. package/compat/cache.cjs +2 -1
  66. package/compat/cache.d.cts +4 -0
  67. package/compat/cookies.cjs +21 -5
  68. package/compat/data-cache.cjs +12 -8
  69. package/compat/flight-delta.cjs +150 -0
  70. package/compat/headers.cjs +4 -2
  71. package/compat/incremental-cache.cjs +20 -7
  72. package/compat/index.d.ts +68 -3
  73. package/compat/link.cjs +12 -9
  74. package/compat/navigation-authorization.cjs +9 -0
  75. package/compat/navigation-stages.cjs +19 -0
  76. package/compat/offline-store.cjs +11 -0
  77. package/compat/offline.cjs +5 -0
  78. package/compat/offline.d.cts +2 -0
  79. package/compat/redis-cache.cjs +201 -0
  80. package/compat/redis-cache.d.cts +37 -0
  81. package/compat/server-primitives.cjs +31 -11
  82. package/compat/server.d.cts +15 -2
  83. package/compat/static-generation.cjs +7 -0
  84. package/compat/use-cache.cjs +3 -0
  85. package/docs/ai/compatibility.md +171 -2
  86. package/docs/ai/data.md +165 -5
  87. package/docs/ai/deployment.md +158 -0
  88. package/docs/ai/quickstart.md +29 -4
  89. package/docs/ai/routing.md +82 -0
  90. package/docs/ai/troubleshooting.md +37 -2
  91. package/package.json +26 -7
  92. package/runtime/action-client.mjs +10 -4
  93. package/runtime/action-server.mjs +2 -0
  94. package/runtime/app-client.mjs +212 -65
  95. package/runtime/app-content.mjs +4 -3
  96. package/runtime/app-partial-client.mjs +3 -0
  97. package/runtime/app-partial.mjs +1 -1
  98. package/runtime/app-prefetch-constants.mjs +4 -0
  99. package/runtime/app-prefetch.mjs +2 -3
  100. package/runtime/app-render-key.mjs +5 -0
  101. package/runtime/app-render.mjs +88 -28
  102. package/runtime/app-routing.mjs +22 -9
  103. package/runtime/app-static.mjs +18 -8
  104. package/runtime/app-stream-html.mjs +10 -53
  105. package/runtime/app-style-selection.mjs +34 -0
  106. package/runtime/app-styles.mjs +154 -0
  107. package/runtime/cache-components-rsc.mjs +4 -1
  108. package/runtime/cache-request.mjs +2 -1
  109. package/runtime/deployment-client.mjs +19 -0
  110. package/runtime/dev-client.mjs +7 -2
  111. package/runtime/dev-compiler-broker.mjs +42 -0
  112. package/runtime/dev-indicator.mjs +8 -3
  113. package/runtime/dev-lazy-tool.mjs +16 -0
  114. package/runtime/development.mjs +29 -12
  115. package/runtime/document.mjs +1 -1
  116. package/runtime/edge.mjs +6 -5
  117. package/runtime/fetch-cache.mjs +10 -8
  118. package/runtime/flight-delta.mjs +50 -0
  119. package/runtime/flight-tap.mjs +28 -0
  120. package/runtime/html-only.mjs +27 -0
  121. package/runtime/http.mjs +2 -0
  122. package/runtime/incremental-cache.mjs +25 -4
  123. package/runtime/instrumentation.mjs +49 -0
  124. package/runtime/navigation-recovery.mjs +36 -0
  125. package/runtime/navigation.mjs +7 -0
  126. package/runtime/node-html-stream.mjs +47 -0
  127. package/runtime/pages-client.mjs +14 -7
  128. package/runtime/pages-navigation.mjs +48 -63
  129. package/runtime/prefetch-scheduler.mjs +110 -0
  130. package/runtime/profile.mjs +14 -0
  131. package/runtime/profiles.json +2 -2
  132. package/runtime/render.mjs +3 -0
  133. package/runtime/route-pattern.mjs +44 -0
  134. package/runtime/rsc-worker.mjs +34 -12
  135. package/runtime/trace.mjs +78 -0
  136. package/runtime/watch-project.mjs +19 -15
  137. package/runtime/worker.mjs +70 -26
@@ -1,19 +1,25 @@
1
- let referenceFactory;
2
- let transport;
1
+ // A newly opened route can move this helper into a different development ESM
2
+ // chunk. Its action proxies still belong to the already mounted application.
3
+ // Production keeps the ordinary per-graph module state.
4
+ const state = process.env.NODE_ENV === 'development'
5
+ ? globalThis[Symbol.for('prnext.development.server-actions')] ||= {}
6
+ : {};
3
7
 
4
8
  // App bootstrap installs the official Flight client after its module loader is
5
9
  // ready. Action proxy modules load through that same client graph afterwards.
6
10
  export function configureServerActions({ createServerReference, callServer }) {
7
- referenceFactory = createServerReference;
8
- transport = callServer;
11
+ state.referenceFactory = createServerReference;
12
+ state.transport = callServer;
9
13
  }
10
14
 
11
15
  export function callServer(id, args) {
16
+ const transport = state.transport;
12
17
  if (!transport) return Promise.reject(new Error('The PRNext Server Action transport is not initialized'));
13
18
  return transport(id, args);
14
19
  }
15
20
 
16
21
  export function createServerReference(id) {
22
+ const referenceFactory = state.referenceFactory;
17
23
  if (!referenceFactory) throw new Error('Server Action references must load after the PRNext Flight client');
18
24
  return referenceFactory(id, callServer);
19
25
  }
@@ -1,3 +1,4 @@
1
+ import {reportRequestError} from './instrumentation.mjs';
1
2
  import { decodeReply, decodeAction, decodeFormState, createTemporaryReferenceSet } from 'react-server-dom-webpack/server.node';
2
3
  import { pathToFileURL } from 'node:url';
3
4
  import path from 'node:path';
@@ -126,6 +127,7 @@ export async function invokeAction(request) {
126
127
  } else if (navigation?.status === 404) {
127
128
  actionNotFound = true;
128
129
  } else {
130
+ reportRequestError(error,{routeType:'action'});
129
131
  console.error('[prnext]', error?.stack || error);
130
132
  actionError = errorPayload(error, request.production);
131
133
  failure = { message: error?.message || String(error), stack: error?.stack, digest: error?.digest };
@@ -1,7 +1,7 @@
1
+ import { deploymentHeaders, assertDeploymentResponse } from './deployment-client.mjs';
1
2
  import React, { startTransition, useLayoutEffect } from 'react';
2
3
  import { preload } from 'react-dom';
3
- import { partialModel } from './app-partial-model.mjs';
4
- import { PARTIAL_PREFETCH_TYPE, PARTIAL_PREFETCH_CACHE_BYTES, readPartialPrefetch, partialFlightStream } from './app-prefetch.mjs';
4
+ import { PARTIAL_PREFETCH_TYPE, PARTIAL_PREFETCH_CACHE_BYTES } from './app-prefetch-constants.mjs';
5
5
  import { isDynamicBailout } from '../compat/dynamic-bailout.cjs';
6
6
  import { createRoot, hydrateRoot } from 'react-dom/client';
7
7
  import { AppRouterProvider } from '../compat/app-context.cjs';
@@ -13,6 +13,14 @@ import { flightResponseURL, publicNavigationURL, isApplicationURL } from './clie
13
13
  import { normalizeBasePath, removeBasePath } from '../compat/paths.cjs';
14
14
  import GlobalErrorBoundary, { DefaultGlobalError, GlobalErrorTrigger } from '../compat/app-global-error-boundary.cjs';
15
15
  import { loadBeforeInteractive } from '../compat/script-loader.cjs';
16
+ import { createPrefetchScheduler, prefetchCacheVictim } from './prefetch-scheduler.mjs';
17
+ import { appStyleManager } from './app-styles.mjs';
18
+ import { retainAppStyles } from './app-style-selection.mjs';
19
+ import { isHtmlDocumentRoute } from './route-pattern.mjs';
20
+ import {recoverNavigation} from './navigation-recovery.mjs';
21
+ import { createActivityCache } from '../compat/activity-cache.cjs';
22
+ import { createFlightTransport, TYPE as FLIGHT_DELTA_TYPE } from '../compat/flight-delta.cjs';
23
+ import {decodeBase64} from '../compat/base64.cjs';
16
24
 
17
25
  const PREFETCH_LIMIT = 8;
18
26
  const PREFETCH_LIFETIME = 30_000;
@@ -88,8 +96,7 @@ function embeddedFlight() {
88
96
  document.removeEventListener('DOMContentLoaded', onComplete);
89
97
  controller.close();
90
98
  } else {
91
- const binary = atob(value);
92
- controller.enqueue(Uint8Array.from(binary, character => character.charCodeAt(0)));
99
+ controller.enqueue(decodeBase64(value));
93
100
  }
94
101
  };
95
102
  onComplete = () => {
@@ -108,28 +115,27 @@ function embeddedFlight() {
108
115
  }
109
116
  const element = document.getElementById('__PRNEXT_FLIGHT__');
110
117
  if (!element) throw new Error('PRNext App Router Flight payload is missing');
111
- const binary = atob(element.textContent.trim());
112
- const bytes = Uint8Array.from(binary, character => character.charCodeAt(0));
118
+ const bytes = decodeBase64(element.textContent.trim());
113
119
  element.remove();
114
120
  return new ReadableStream({ start(controller) { controller.enqueue(bytes); controller.close(); } });
115
121
  }
116
122
 
117
123
  function NormalStyles({ css, fonts }) {
118
- const [styles, setStyles] = React.useState([]);
119
124
  useLayoutEffect(() => {
120
125
  for (const font of fonts || []) preload(font.href, { as: 'font', type: font.type, crossOrigin: 'anonymous' });
121
- // Fatal global renders never commit this branch. Wait for a successful
122
- // normal/local-error mount, then restore sheets omitted from its empty SSR
123
- // document. Existing plain SSR links are already loaded and need no copy.
124
- const loaded = new Set([...document.querySelectorAll('link[rel="stylesheet"]')].map(node => node.href));
125
- const missing = (css || []).filter(href => !loaded.has(new URL(href, window.location.href).href));
126
- if (missing.length) setStyles(current => [...new Set([...current, ...missing])]);
126
+ const styles = appStyleManager(document);
127
+ styles.commit(css || []);
128
+ return () => styles.release();
127
129
  }, [css, fonts]);
128
- return styles.map(href => React.createElement('link', { key: href, href, rel: 'stylesheet', precedence: 'prnext-app' }));
130
+ return null;
129
131
  }
130
132
 
131
- function AppRoot({ model, controller, committed, url, globalErrorComponent, globalErrorCss }) {
133
+ function AppRoot({ model, controller, committed, url, globalErrorComponent, globalErrorCss, retentionEpoch, retentionAuthorizationEpoch }) {
132
134
  const [hydrated, setHydrated] = React.useState(false);
135
+ const cache = React.useRef(null);
136
+ if (!cache.current) cache.current = createActivityCache({ limit: controller._navigationCache?.maxEntries, ttl: controller._navigationCache?.ttlMs });
137
+ const activityCache = React.useMemo(() => ({ cache: cache.current, epoch: retentionEpoch, authorizationEpoch: retentionAuthorizationEpoch,
138
+ authorizationKey: model.authorizationKey, strictAuthorization: controller._navigationCache?.authCookies !== undefined }), [retentionEpoch, retentionAuthorizationEpoch, model.authorizationKey]);
133
139
  const previewDynamic = React.useCallback(() => { throw model.partialPreview.suspended; }, [model.partialPreview]);
134
140
  useLayoutEffect(() => {
135
141
  if (process.env.NODE_ENV === 'development' && model.instantDiagnostics?.length) globalThis.__PRNEXT_DEV__?.reportError(Object.assign(new Error(model.instantDiagnostics.join('\n')), { digest: 'PRNEXT_INSTANT_BLOCKING' }));
@@ -145,10 +151,11 @@ function AppRoot({ model, controller, committed, url, globalErrorComponent, glob
145
151
  return React.createElement(AppRouterProvider, { router, controller, ...(model.partialPreview ? { prerenderSearch: previewDynamic, ...(model.partialPreview.unknownParams ? { prerenderParams: previewDynamic } : {}) } : {}) },
146
152
  React.createElement(GlobalErrorBoundary, { component: globalErrorComponent, css: globalErrorCss, resetKey: model },
147
153
  model.globalError ? React.createElement(GlobalErrorTrigger, { error: model.globalError })
148
- : appContent(model.tree, { layoutCache: model.layoutCache || null, styles: React.createElement(NormalStyles, { css: model.css, fonts: model.fonts }) })));
154
+ : appContent(model.tree, { layoutCache: model.layoutCache || null, activityCache, styles: React.createElement(NormalStyles, { css: model.css, fonts: model.fonts }) })));
149
155
  }
150
156
 
151
157
  function prepareLayoutCache(model, previous) {
158
+ retainAppStyles(model, previous);
152
159
  if (!model.routing || model.layoutCache) return model;
153
160
  const active = new Set(Object.keys(model.routing.slots));
154
161
  model.layoutCache = {
@@ -166,7 +173,7 @@ function AppView(props) {
166
173
  }
167
174
 
168
175
  function appView(props) {
169
- return React.createElement(strictMode ? React.StrictMode : React.Fragment, null, React.createElement(AppView, props));
176
+ return React.createElement(strictMode ? React.StrictMode : React.Fragment, null, React.createElement(AppView, { ...props, retentionEpoch: props.controller?._retentionEpoch || 0, retentionAuthorizationEpoch: props.controller?._authorizationEpoch || 0 }));
170
177
  }
171
178
 
172
179
  function historyState(key, scroll, routing) {
@@ -179,7 +186,7 @@ function scrollToTarget(url, saved) {
179
186
  if (url.hash) {
180
187
  let name = url.hash.slice(1);
181
188
  try { name = decodeURIComponent(name); } catch { /* A literal malformed escape can be an element ID. */ }
182
- const anchor = document.getElementById(name) || document.getElementsByName(name)[0];
189
+ const anchor = [...document.querySelectorAll('[id], [name]')].find(element => (element.id === name || element.getAttribute('name') === name) && element.getClientRects().length);
183
190
  if (anchor) { anchor.scrollIntoView(); return; }
184
191
  }
185
192
  window.scrollTo(0, 0);
@@ -191,7 +198,7 @@ function assertModel(model) {
191
198
  return model;
192
199
  }
193
200
 
194
- async function startApp({ clientModules = {}, basePath = '', trailingSlash = false, skipTrailingSlashRedirect = false, globalErrorId, globalErrorCss = [], strictMode: enabledStrictMode = true, cacheComponents = false } = {}) {
201
+ async function startApp({ deploymentId, htmlOnlyRoutes, clientModules = {}, basePath = '', trailingSlash = false, skipTrailingSlashRedirect = false, globalErrorId, globalErrorCss = [], strictMode: enabledStrictMode = true, cacheComponents = false, partialPrefetching = false, navigationCache } = {}) {
195
202
  strictMode = enabledStrictMode;
196
203
  await loadBeforeInteractive();
197
204
  basePath = normalizeBasePath(basePath);
@@ -201,8 +208,18 @@ async function startApp({ clientModules = {}, basePath = '', trailingSlash = fal
201
208
  await globalThis.__webpack_chunk_load__(globalErrorId);
202
209
  return { default: globalThis.__webpack_require__(globalErrorId).default };
203
210
  }) : DefaultGlobalError;
211
+ // The production browser graph fixes this feature at build time. The
212
+ // fallback also keeps direct, unbundled runtime consumers functional.
213
+ const partialRuntime = typeof __PRNEXT_CACHE_COMPONENTS__ === 'undefined'
214
+ ? cacheComponents ? import('./app-partial-client.mjs') : null
215
+ : __PRNEXT_CACHE_COMPONENTS__ ? import('./app-partial-client.mjs') : null;
204
216
  // The Flight adapter reads __webpack_require__.u during module evaluation.
205
- const flightClient = await import('react-server-dom-webpack/client.browser');
217
+ // Both imports start concurrently and receive rejection handlers in this
218
+ // turn. A rejected PPR chunk must not wait unhandled for a slow Flight chunk.
219
+ const [flightClient, partial] = await Promise.all([import('react-server-dom-webpack/client.browser'), partialRuntime]);
220
+ // Ordinary App routes do not need the resumable PPR model. Keep it out of
221
+ // their initial module graph; Cache Components still loads it before use.
222
+ const { partialModel, readPartialPrefetch, partialFlightStream } = partial || {};
206
223
  const { createFromFetch, createFromReadableStream, createServerReference, createTemporaryReferenceSet, encodeReply } = flightClient.default || flightClient;
207
224
  let root;
208
225
  let actionQueue = Promise.resolve();
@@ -222,16 +239,15 @@ async function startApp({ clientModules = {}, basePath = '', trailingSlash = fal
222
239
  let currentUrl = new URL(window.location.href);
223
240
  let currentRouting = initialModel.routing;
224
241
  let currentModel = initialModel;
225
- const historyModels = new Map();
242
+ const historyFocus = new Map();
226
243
  let currentKey = window.history.state?.__prnext?.key || 0;
227
244
  let nextKey = currentKey + 1;
228
245
  let navigation = 0;
229
- let activeRequest;
230
- const prefetches = new Map();
231
- const partialSegments = new Map();
246
+ let activeRequest, finishNavigation, pageHidden = false, notifyingPrefetch = false;
247
+ const prefetches = new Map(), scheduler = createPrefetchScheduler();
248
+ const partialSegments = (typeof __PRNEXT_CACHE_COMPONENTS__ === 'undefined' ? cacheComponents : __PRNEXT_CACHE_COMPONENTS__) ? new Map() : null;
232
249
  let segmentBytes = 0;
233
250
 
234
- if (currentRouting) historyModels.set(currentKey, initialModel);
235
251
  window.history.replaceState(historyState(currentKey, [window.scrollX, window.scrollY], currentRouting), '', currentUrl.href);
236
252
  window.history.scrollRestoration = 'manual';
237
253
 
@@ -239,43 +255,62 @@ async function startApp({ clientModules = {}, basePath = '', trailingSlash = fal
239
255
  // popstate has already changed location; never overwrite its destination.
240
256
  if (window.location.href === currentUrl.href) {
241
257
  window.history.replaceState(historyState(currentKey, [window.scrollX, window.scrollY], currentRouting), '', currentUrl.href);
258
+ if (typeof WeakRef !== 'undefined' && document.activeElement && document.activeElement !== document.body) historyFocus.set(currentKey, new WeakRef(document.activeElement));
259
+ while (historyFocus.size > 16) historyFocus.delete(historyFocus.keys().next().value);
242
260
  }
243
261
  }
244
262
 
245
- async function requestFlight(url, signal, routing = currentRouting, onPartial, {skipEnsure=false}={}) {
263
+ const flightTransport = createFlightTransport();
264
+ async function requestFlight(url, signal, routing = currentRouting, onPartial, {skipEnsure=false,fullFlight=false}={}) {
246
265
  if(process.env.NODE_ENV==='development'&&!routing?.refresh&&!skipEnsure)await globalThis.__PRNEXT_DEV__?.ensureRoute?.(url.href);
247
266
  const exported = window.__PRNEXT_STATIC_EXPORT__ === true;
248
- const target = exported ? new URL(basePath + '/_prnext/flight' + (url.pathname.slice(basePath.length).replace(/\/$/, '') || '') + '/index.txt', url) : url;
267
+ const base = !exported && !fullFlight ? flightTransport.snapshot() : undefined;
268
+ const target = exported ? new URL(basePath + '/_prnext/flight' + (deploymentId ? '/' + encodeURIComponent(deploymentId) : '') + (url.pathname.slice(basePath.length).replace(/\/$/, '') || '') + '/index.txt', url) : url;
249
269
  const response = await fetch(target.href, {
250
- headers: { RSC: '1', Accept: 'text/x-component', ...(routing ? { 'x-prnext-router-state': encodeURIComponent(JSON.stringify(routing)) } : {}) },
270
+ headers: { ...deploymentHeaders(deploymentId), RSC: '1', Accept: 'text/x-component', ...(!exported&&!fullFlight?{'x-prnext-flight-accept':'v1',...(base?{'x-prnext-flight-base':base.token}:{})}:{}), ...(routing ? { 'x-prnext-router-state': encodeURIComponent(JSON.stringify(routing)) } : {}) },
251
271
  credentials: 'same-origin',
252
272
  signal,
253
273
  });
274
+ if (!exported) await assertDeploymentResponse(response, deploymentId);
254
275
  let destination;
255
276
  try { destination = exported ? url : flightResponseURL(response, url); }
256
277
  catch (error) { await response.body?.cancel().catch(() => {}); throw error; }
257
- if (exported ? !response.ok : !response.headers.get('content-type')?.toLowerCase().startsWith('text/x-component')) {
278
+ const delta = response.headers.get('content-type')?.startsWith(FLIGHT_DELTA_TYPE);
279
+ if (exported ? !response.ok : !(delta||response.headers.get('content-type')?.toLowerCase().startsWith('text/x-component'))) {
258
280
  await response.body?.cancel();
259
281
  throw new Error('The navigation destination requires a document request');
260
282
  }
261
- onPartial?.(response, destination);
262
- const model = assertModel(await createFromFetch(Promise.resolve(response), flightOptions));
283
+ await onPartial?.(response, destination);
284
+ let model;
285
+ try { model = assertModel(await createFromFetch(Promise.resolve(exported?response:await flightTransport.response(response,base)), flightOptions)); }
286
+ catch(error) {
287
+ if(!delta||fullFlight||signal?.aborted)throw error;
288
+ flightTransport.clear();
289
+ return requestFlight(url,signal,routing,onPartial,{skipEnsure:true,fullFlight:true});
290
+ }
263
291
  model.router.basePath = basePath;
264
292
  const rewrite = readRewriteHeader(response);
265
293
  if (rewrite) model.rewrite = rewrite;
266
294
  return { model, url: destination };
267
295
  }
268
296
 
269
- async function requestPartial(url, signal) {
297
+ const navigateFlight=(url,signal,...args)=>recoverNavigation(recoverySignal=>requestFlight(url,recoverySignal,...args),{signal});
298
+
299
+ const requestPartial = (typeof __PRNEXT_CACHE_COMPONENTS__ === 'undefined' ? cacheComponents : __PRNEXT_CACHE_COMPONENTS__) ? async function requestPartial(url, signal, full = false) {
270
300
  const response = await fetch(url.href, { credentials: 'same-origin', signal,
271
- headers: { RSC: '1', Accept: PARTIAL_PREFETCH_TYPE, 'x-prnext-prefetch': '1',
301
+ headers: { ...deploymentHeaders(deploymentId), RSC: '1', Accept: PARTIAL_PREFETCH_TYPE, 'x-prnext-prefetch': '1',
302
+ ...(partialPrefetching ? { 'x-prnext-prefetch-stage': full ? 'prefetch' : 'shell' } : {}),
272
303
  ...(partialSegments.size ? { 'x-prnext-prefetch-known': [...partialSegments.keys()].join(',') } : {}) } });
273
- const destination = flightResponseURL(response, url);
304
+ await assertDeploymentResponse(response, deploymentId);
305
+ let destination;
306
+ try { destination = flightResponseURL(response, url); }
307
+ catch (error) { await response.body?.cancel().catch(() => {}); throw error; }
274
308
  if (!response.headers.get('content-type')?.startsWith(PARTIAL_PREFETCH_TYPE)) {
275
309
  await response.body?.cancel();
276
310
  return null;
277
311
  }
278
312
  const value = await readPartialPrefetch(response);
313
+ if (signal.aborted) return null;
279
314
  let segment = partialSegments.get(value.id);
280
315
  if (!segment && value.flight) {
281
316
  const bytes = value.flight.length;
@@ -294,11 +329,13 @@ async function startApp({ clientModules = {}, basePath = '', trailingSlash = fal
294
329
  // No visitor data is decoded here. Explicit postponed errors become pending
295
330
  // boundaries until the navigation's independently authorized Flight arrives.
296
331
  const suspended = new Promise(() => {});
297
- const model = partialModel(assertModel(await segment.model), { keyMap: new Map(value.keys), suspended });
332
+ const decoded = assertModel(await segment.model);
333
+ if (signal.aborted) return null;
334
+ const model = partialModel(decoded, { keyMap: new Map(value.keys), suspended });
298
335
  model.router = { ...value.router, basePath };
299
336
  model.partialPreview = { unknownParams: value.unknownParams, suspended };
300
337
  return { model, id: value.id, url: destination, partial: true };
301
- }
338
+ } : null;
302
339
 
303
340
  function dropPrefetch(key, invalidate = false) {
304
341
  const entry = prefetches.get(key);
@@ -307,13 +344,22 @@ async function startApp({ clientModules = {}, basePath = '', trailingSlash = fal
307
344
  clearTimeout(entry.timer);
308
345
  if (invalidate) {
309
346
  entry.abort.abort();
310
- entry.onInvalidate?.();
347
+ const callbacks = entry.invalidations; entry.invalidations = undefined;
348
+ // Callbacks may rearm prefetch. Deliver after cache/queue mutations so a
349
+ // replacement cannot be evicted inside the same invalidation transaction.
350
+ if (callbacks?.size) queueMicrotask(() => {
351
+ for (const callback of callbacks) {
352
+ notifyingPrefetch = true;
353
+ try { callback(); } catch (error) { console.error(error); }
354
+ finally { notifyingPrefetch = false; }
355
+ }
356
+ });
311
357
  }
312
358
  }
313
359
 
314
360
  function invalidatePrefetches() {
315
- partialSegments.clear(); segmentBytes = 0;
316
- for (const key of prefetches.keys()) dropPrefetch(key, true);
361
+ if (typeof __PRNEXT_CACHE_COMPONENTS__ === 'undefined' ? cacheComponents : __PRNEXT_CACHE_COMPONENTS__) { partialSegments.clear(); segmentBytes = 0; }
362
+ for (const [key, entry] of [...prefetches]) if (prefetches.get(key) === entry) dropPrefetch(key, true);
317
363
  }
318
364
 
319
365
  function callServer(id, args) {
@@ -321,7 +367,10 @@ async function startApp({ clientModules = {}, basePath = '', trailingSlash = fal
321
367
  const token = ++navigation;
322
368
  const target = new URL(window.location.href);
323
369
  activeRequest?.abort();
370
+ appStyleManager(document).cancel();
371
+ finishNavigation?.(); finishNavigation = undefined;
324
372
  activeRequest = undefined;
373
+ purgeRetainedPages();
325
374
  // Mutations are dispatched in order, including Set-Cookie effects required
326
375
  // by a subsequent action. Capture their route priority when invoked: a
327
376
  // queued action must not cancel a newer user navigation when it starts.
@@ -331,12 +380,17 @@ async function startApp({ clientModules = {}, basePath = '', trailingSlash = fal
331
380
  }
332
381
 
333
382
  async function dispatchAction(id, args, token, target) {
383
+ const release = scheduler.navigation();
384
+ try { return await performAction(id, args, token, target); } finally { release(); }
385
+ }
386
+
387
+ async function performAction(id, args, token, target) {
334
388
  invalidatePrefetches();
335
389
  const temporaryReferences = createTemporaryReferenceSet();
336
390
  const body = await encodeReply(args, { temporaryReferences });
337
391
  const response = await fetch(target.href, {
338
392
  method: 'POST',
339
- headers: { 'Next-Action': id, RSC: '1', Accept: 'text/x-component', ...(currentRouting ? { 'x-prnext-router-state': encodeURIComponent(JSON.stringify({ ...currentRouting, refresh: true })) } : {}) },
393
+ headers: { ...deploymentHeaders(deploymentId), 'Next-Action': id, RSC: '1', Accept: 'text/x-component', ...(currentRouting ? { 'x-prnext-router-state': encodeURIComponent(JSON.stringify({ ...currentRouting, refresh: true })) } : {}) },
340
394
  credentials: 'same-origin',
341
395
  // RPC redirects arrive as Flight data. Never replay a mutation through an
342
396
  // unexpected HTTP 307/308 or redirect an authenticated POST off origin.
@@ -344,11 +398,13 @@ async function startApp({ clientModules = {}, basePath = '', trailingSlash = fal
344
398
  body,
345
399
  });
346
400
  invalidatePrefetches();
401
+ await assertDeploymentResponse(response, deploymentId);
347
402
  if (!response.headers.get('content-type')?.toLowerCase().startsWith('text/x-component')) {
348
403
  await response.body?.cancel();
349
404
  throw new Error(`Server Action request failed (${response.status})`);
350
405
  }
351
406
  const model = await createFromFetch(Promise.resolve(response), { ...flightOptions, temporaryReferences });
407
+ controller._retentionEpoch++;
352
408
  if (model?.routingInvalid || (model?.rootLayout && currentModel.rootLayout && model.rootLayout !== currentModel.rootLayout)) {
353
409
  if (token === navigation) window.location.replace(target.href);
354
410
  return model.actionResult;
@@ -364,9 +420,12 @@ async function startApp({ clientModules = {}, basePath = '', trailingSlash = fal
364
420
  const rewrite = readRewriteHeader(response);
365
421
  if (rewrite) model.rewrite = rewrite;
366
422
  prepareLayoutCache(model, currentModel);
423
+ try { await appStyleManager(document).prepare(model.globalError ? [] : model.css || []); }
424
+ catch (error) { if (token !== navigation) return model.actionResult; throw error; }
425
+ if (token !== navigation) return model.actionResult;
367
426
  currentRouting = model.routing;
368
427
  currentModel = model;
369
- if (currentRouting) historyModels.set(currentKey, model);
428
+ appStyleManager(document).handoff(model.globalError ? [] : model.css || []);
370
429
  startTransition(() => root.render(appView({ model, controller, url: target.href, globalErrorComponent, globalErrorCss })));
371
430
  }
372
431
  if (model?.actionError) {
@@ -383,14 +442,17 @@ async function startApp({ clientModules = {}, basePath = '', trailingSlash = fal
383
442
  }
384
443
 
385
444
  async function navigate(href, { replace = false, scroll = true, pop = false, savedScroll, savedRouting, refresh = false } = {}) {
445
+ if (pageHidden) return;
386
446
  const target = navigationUrl(href);
387
- if (!isApplicationURL(target, window.location.origin, basePath)) {
447
+ if (!isApplicationURL(target, window.location.origin, basePath) || isHtmlDocumentRoute(htmlOnlyRoutes, removeBasePath(target.pathname, basePath))) {
388
448
  if (replace) window.location.replace(target.href);
389
449
  else window.location.assign(target.href);
390
450
  return;
391
451
  }
392
452
  const token = ++navigation;
393
453
  activeRequest?.abort();
454
+ appStyleManager(document).cancel();
455
+ finishNavigation?.(); finishNavigation = undefined;
394
456
  activeRequest = undefined;
395
457
  saveScroll();
396
458
 
@@ -414,31 +476,47 @@ async function startApp({ clientModules = {}, basePath = '', trailingSlash = fal
414
476
  return;
415
477
  }
416
478
 
479
+ const cacheKey = `${target.origin}${target.pathname}${target.search}`;
480
+ const candidate = !refresh && prefetches.get(cacheKey);
481
+ const prefetched = candidate && !candidate.abort.signal.aborted ? candidate : undefined;
482
+ if (candidate && !prefetched) dropPrefetch(cacheKey, true);
483
+ const releaseNavigation = scheduler.navigation(prefetched?.task);
484
+ finishNavigation = releaseNavigation;
417
485
  try {
418
- const cacheKey = `${target.origin}${target.pathname}${target.search}`;
419
- const prefetched = !refresh && prefetches.get(cacheKey);
420
486
  let result;
421
487
  let historyUpdated = false;
422
- const historyModel = pop && savedRouting && historyModels.get(window.history.state?.__prnext?.key);
423
- if (historyModel) result = { model: historyModel, url: target };
424
- else if (prefetched) {
488
+ // Always authorize history navigation again. Mounted branches can retain
489
+ // UI state while fresh Flight verifies cookies, middleware and rewrites.
490
+ if (prefetched) {
425
491
  activeRequest = prefetched.abort;
426
492
  dropPrefetch(cacheKey);
427
- result = await prefetched.promise;
493
+ try { result = await prefetched.promise; }
494
+ catch (error) { if (token !== navigation || (prefetched.abort.signal.aborted && !prefetched.failed)) throw error; result = null; }
428
495
  if (result && !result.partial) {
429
- result = { ...result, url: new URL(result.url.href) };
430
- if (!result.url.hash) result.url.hash = target.hash;
496
+ if (result.model.authorizationKey) {
497
+ // A full private prefetch warmed modules/data, not authorization.
498
+ // HttpOnly cookies may have changed since it was produced.
499
+ const abort = new AbortController(); activeRequest = abort;
500
+ result = await navigateFlight(target, abort.signal, pop && savedRouting ? { ...savedRouting, restore: true } : currentRouting);
501
+ } else {
502
+ result = { ...result, url: new URL(result.url.href) };
503
+ if (!result.url.hash) result.url.hash = target.hash;
504
+ }
431
505
  } else {
432
506
  const preview = result;
433
507
  const abort = new AbortController();
434
508
  activeRequest = abort;
435
- result = await requestFlight(target, abort.signal, currentRouting, (response, destination) => {
509
+ result = await navigateFlight(target, abort.signal, currentRouting, async (response, destination) => {
436
510
  if (!preview || token !== navigation || response.headers.get('x-prnext-ppr-id') !== preview.id ||
437
511
  (preview.model.rootLayout && currentModel.rootLayout && preview.model.rootLayout !== currentModel.rootLayout)) return;
438
512
  // Auth, middleware and rewrites ran again. A changed target or a
439
513
  // redirect cannot reveal an obsolete protected prefetched segment.
440
514
  if (destination.pathname !== preview.url.pathname || destination.search !== preview.url.search) return;
515
+ prepareLayoutCache(preview.model, currentModel);
516
+ await appStyleManager(document).prepare(preview.model.css || [], abort.signal);
517
+ if (token !== navigation || abort.signal.aborted) return;
441
518
  updateHistory(destination); historyUpdated = true;
519
+ appStyleManager(document).handoff(preview.model.css || []);
442
520
  startTransition(() => root.render(appView({ model: preview.model, controller, url: destination.href, globalErrorComponent, globalErrorCss })));
443
521
  });
444
522
  }
@@ -448,54 +526,98 @@ async function startApp({ clientModules = {}, basePath = '', trailingSlash = fal
448
526
  // A refresh is already inside the compiler's apply transaction. Calling
449
527
  // ensureRoute from here can wait for that same transaction forever when
450
528
  // this tree has no routing state (the ordinary single-route case).
451
- result = await requestFlight(target, abort.signal, pop && savedRouting ? { ...savedRouting, restore: true } : refresh && currentRouting ? { ...currentRouting, refresh: true } : currentRouting, undefined, {skipEnsure:refresh});
529
+ result = await navigateFlight(target, abort.signal, pop && savedRouting ? { ...savedRouting, restore: true } : refresh && currentRouting ? { ...currentRouting, refresh: true } : currentRouting, undefined, {skipEnsure:refresh});
452
530
  }
453
531
  if (token !== navigation) return;
454
532
  if (!isApplicationURL(result.url, window.location.origin, basePath)) throw new Error('The navigation destination requires a document request');
455
533
  if (result.model.rootLayout && currentModel.rootLayout && result.model.rootLayout !== currentModel.rootLayout) throw new Error('A different root layout requires a document request');
456
534
  prepareLayoutCache(result.model, currentModel);
535
+ await appStyleManager(document).prepare(result.model.globalError ? [] : result.model.css || [], activeRequest?.signal);
536
+ if (token !== navigation) return;
457
537
  currentRouting = result.model.routing;
458
538
  currentModel = result.model;
459
539
  if (currentRouting) invalidatePrefetches();
460
540
  if (!historyUpdated) updateHistory(result.url);
461
- if (currentRouting) historyModels.set(currentKey, currentModel);
462
- while (historyModels.size > 16) historyModels.delete(historyModels.keys().next().value);
463
541
  const committed = () => {
464
- if (token === navigation && scroll && !refresh) scrollToTarget(result.url, savedScroll);
542
+ if (token === navigation && scroll && !refresh) {
543
+ scrollToTarget(result.url, savedScroll);
544
+ const focus = pop && historyFocus.get(currentKey)?.deref();
545
+ if (focus?.isConnected && focus.getClientRects().length) focus.focus({ preventScroll: true });
546
+ }
465
547
  };
548
+ appStyleManager(document).handoff(result.model.globalError ? [] : result.model.css || []);
466
549
  if(process.env.NODE_ENV==='development'&&refresh)await new Promise(resolve=>startTransition(()=>root.render(appView({model:result.model,controller,committed:()=>{committed();resolve();},url:result.url.href,globalErrorComponent,globalErrorCss}))));
467
550
  else startTransition(() => root.render(appView({ model: result.model, controller, committed, url: result.url.href, globalErrorComponent, globalErrorCss })));
468
551
  } catch (error) {
469
552
  if (token !== navigation || error?.name === 'AbortError') return;
553
+ if(error?.code==='PRNEXT_NAVIGATION_NETWORK'){
554
+ window.dispatchEvent(new CustomEvent('prnext:navigation-error',{detail:{message:error.message}}));
555
+ return;
556
+ }
470
557
  // Pages Router routes, public files, and unavailable Flight endpoints use
471
558
  // a normal document navigation, including the browser's redirect handling.
472
559
  if (replace || pop || refresh) window.location.replace(target.href);
473
560
  else window.location.assign(target.href);
561
+ } finally {
562
+ if (finishNavigation === releaseNavigation) finishNavigation = undefined;
563
+ releaseNavigation();
474
564
  }
475
565
  }
476
566
 
567
+ function purgeRetainedPages() {
568
+ controller._retentionEpoch++;
569
+ historyFocus.clear();
570
+ startTransition(() => root?.render(appView({ model: currentModel, controller, url: currentUrl.href, globalErrorComponent, globalErrorCss })));
571
+ }
572
+
477
573
  const controller = {
478
- partialPrefetch: cacheComponents,
574
+ _retentionEpoch: 0,
575
+ _authorizationEpoch: 0,
576
+ _navigationCache: navigationCache,
577
+ partialPrefetch: (typeof __PRNEXT_CACHE_COMPONENTS__ === 'undefined' ? cacheComponents : __PRNEXT_CACHE_COMPONENTS__),
479
578
  push: (href, options) => { void navigate(navigationUrl(href), options); },
480
579
  replace: (href, options) => { void navigate(navigationUrl(href), { ...options, replace: true }); },
481
580
  refresh: () => {
482
581
  invalidatePrefetches();
582
+ purgeRetainedPages();
483
583
  void navigate(new URL(window.location.href), { refresh: true });
484
584
  },
485
585
  back: () => { saveScroll(); window.history.back(); },
486
586
  forward: () => { saveScroll(); window.history.forward(); },
487
587
  prefetch(href, options = {}) {
588
+ if (pageHidden) return;
488
589
  const target = navigationUrl(href);
489
- if (!isApplicationURL(target, window.location.origin, basePath)) return;
590
+ if (!isApplicationURL(target, window.location.origin, basePath) || isHtmlDocumentRoute(htmlOnlyRoutes, removeBasePath(target.pathname, basePath))) return;
490
591
  target.hash = '';
491
592
  const key = target.href;
492
- if (prefetches.has(key)) return;
493
- while (prefetches.size >= PREFETCH_LIMIT) dropPrefetch(prefetches.keys().next().value, true);
593
+ const priority = options.priority === 'viewport' ? 0 : 1;
594
+ const previous = prefetches.get(key);
595
+ if (previous && !previous.abort.signal.aborted && (!options.full || previous.full)) {
596
+ previous.priority = Math.max(previous.priority, priority);
597
+ previous.task.promote(options.priority);
598
+ if (typeof options.onInvalidate === 'function') previous.invalidations.add(options.onInvalidate);
599
+ return;
600
+ }
601
+ if (previous) dropPrefetch(key, true);
602
+ // Self-rearming callbacks may fill free slots, but cannot keep evicting
603
+ // each other forever when more warm routes compete for a bounded cache.
604
+ if (notifyingPrefetch && prefetches.size >= PREFETCH_LIMIT) return;
605
+ const victim = prefetches.size >= PREFETCH_LIMIT ? prefetchCacheVictim(prefetches, priority) : undefined;
606
+ if (prefetches.size >= PREFETCH_LIMIT && victim === undefined) return;
494
607
  const abort = new AbortController();
495
- const entry = { abort, onInvalidate: options.onInvalidate };
496
- entry.promise = cacheComponents && !window.__PRNEXT_STATIC_EXPORT__ ? requestPartial(target, abort.signal) : requestFlight(target, abort.signal);
608
+ const entry = { abort, priority, full: options.full === true, invalidations: new Set(typeof options.onInvalidate === 'function' ? [options.onInvalidate] : []) };
609
+ entry.task = scheduler.schedule(() => (typeof __PRNEXT_CACHE_COMPONENTS__ === 'undefined' ? cacheComponents : __PRNEXT_CACHE_COMPONENTS__) && !window.__PRNEXT_STATIC_EXPORT__
610
+ ? options.full && !partialPrefetching ? requestFlight(target, abort.signal) : requestPartial(target, abort.signal, options.full) : requestFlight(target, abort.signal), { abort, priority: options.priority });
611
+ if (!entry.task) return;
612
+ if (victim !== undefined) dropPrefetch(victim, true);
613
+ entry.promise = entry.task.promise;
497
614
  // A failed speculative fetch must never produce an unhandled rejection.
498
- entry.promise.catch(() => { if (prefetches.get(key) === entry) dropPrefetch(key, true); });
615
+ entry.promise.catch(error => {
616
+ // An intentional cancellation is not a cache miss to retry. In
617
+ // particular pagehide must never create a replacement Flight request.
618
+ entry.failed = !abort.signal.aborted && error?.name !== 'AbortError';
619
+ if (prefetches.get(key) === entry) dropPrefetch(key, true);
620
+ });
499
621
  entry.timer = setTimeout(() => dropPrefetch(key, true), PREFETCH_LIFETIME);
500
622
  prefetches.set(key, entry);
501
623
  },
@@ -528,16 +650,36 @@ async function startApp({ clientModules = {}, basePath = '', trailingSlash = fal
528
650
  window.addEventListener('popstate', event => {
529
651
  void navigate(new URL(window.location.href), { pop: true, savedScroll: event.state?.__prnext?.scroll, savedRouting: event.state?.__prnext?.routing });
530
652
  });
531
- window.addEventListener('pagehide', saveScroll);
653
+ window.addEventListener('pagehide', () => { saveScroll(); pageHidden = true; navigation++; activeRequest?.abort(); appStyleManager(document).cancel(); finishNavigation?.(); finishNavigation = undefined; invalidatePrefetches(); });
654
+ window.addEventListener('pageshow', event => { pageHidden = false; if (event.persisted) controller.refresh(); });
655
+ window.addEventListener('prnext:invalidate-navigation', () => { controller._authorizationEpoch++; controller.refresh(); });
532
656
  let scrollFrame;
533
657
  window.addEventListener('scroll', () => {
534
658
  if (scrollFrame !== undefined) return;
535
659
  scrollFrame = requestAnimationFrame(() => { scrollFrame = undefined; saveScroll(); });
536
660
  }, { passive: true });
661
+ function developmentStyles(model, routes) {
662
+ const route = routes?.find(route => route.pattern === model.styleRoute);
663
+ if (!route) return model;
664
+ const scopes = routes.flatMap(route => route.cssScopes || []);
665
+ const previous = { routing:model.routing, cssScopes:(model.cssScopes || []).map(scope => scopes.find(current => current.path === scope.path && current.kind === scope.kind) || scope) };
666
+ const next = { ...model, css:route.css, cssScopes:route.cssScopes };
667
+ retainAppStyles(next, previous);
668
+ return next;
669
+ }
537
670
  return { root, router: controller, ...(process.env.NODE_ENV === 'development' ? {
538
671
  devReady,
672
+ devStyles: routes => developmentStyles(currentModel, routes).css || [],
673
+ devPrepareStyles: routes => appStyleManager(document).prepare(developmentStyles(currentModel, routes).css || []),
674
+ devStylesApplied: (routes, { commit = true } = {}) => {
675
+ for (const model of [currentModel]) {
676
+ const next = developmentStyles(model, routes);
677
+ model.css = next.css; model.cssScopes = next.cssScopes;
678
+ }
679
+ if (commit) appStyleManager(document).commit(currentModel.globalError ? [] : currentModel.css || []);
680
+ },
539
681
  devPrepare: options => moduleLoader.updateModules(options.clientModules),
540
- devCommit: () => {invalidatePrefetches();return navigate(new URL(window.location.href),{refresh:true});},
682
+ devCommit: () => {invalidatePrefetches();purgeRetainedPages();return navigate(new URL(window.location.href),{refresh:true});},
541
683
  } : {}) };
542
684
  }
543
685
 
@@ -545,6 +687,11 @@ export function bootstrapApp(options) {
545
687
  if (process.env.NODE_ENV === 'development' && options.dev && globalThis.__PRNEXT_DEV__) {
546
688
  return globalThis.__PRNEXT_DEV__.bootstrap('app', options, () => startApp(options));
547
689
  }
548
- application ||= startApp(options);
690
+ if (!application) {
691
+ application = startApp(options);
692
+ // Generated entries intentionally do not await startup. Report a failed
693
+ // chunk once, while retaining the original rejection for explicit callers.
694
+ void application.catch(error => console.error('[PRNext] App startup failed:', error));
695
+ }
549
696
  return application;
550
697
  }
@@ -1,8 +1,9 @@
1
1
  import React from 'react';
2
- import { LayoutCacheContext } from '../compat/app-layout-context.cjs';
2
+ import { LayoutCacheContext, ActivityCacheContext } from '../compat/app-layout-context.cjs';
3
3
 
4
4
  // Preserve the same sibling positions during SSR, PPR and hydration. Even an
5
5
  // empty style slot changes React's useId tree encoding when it is omitted.
6
- export function appContent(tree, { layoutCache = null, styles = null } = {}) {
7
- return React.createElement(LayoutCacheContext.Provider, { value: layoutCache }, tree, styles);
6
+ export function appContent(tree, { layoutCache = null, styles = null, activityCache = null } = {}) {
7
+ return React.createElement(ActivityCacheContext.Provider, { value: activityCache },
8
+ React.createElement(LayoutCacheContext.Provider, { value: layoutCache }, tree, styles));
8
9
  }
@@ -0,0 +1,3 @@
1
+ // Loaded once, before hydration, only for Cache Components applications.
2
+ export { partialModel } from './app-partial-model.mjs';
3
+ export { readPartialPrefetch, partialFlightStream } from './app-prefetch.mjs';
@@ -74,7 +74,7 @@ export async function prerenderPartialHtml(model, route, { strictMode = true, un
74
74
  if (!/<html(?:\s|>)/i.test(shell) || !/<body(?:\s|>)/i.test(shell) || !/<\/head>/i.test(shell)) {
75
75
  return null;
76
76
  }
77
- const styles = fontPreloads(route.fonts) + (route.css || []).map(href => `<link rel="stylesheet" href="${escapeHtml(href)}">`).join('');
77
+ const styles = fontPreloads(route.fonts) + (model.css ?? route.css ?? []).map(href => `<link rel="stylesheet" href="${escapeHtml(href)}">`).join('');
78
78
  if (styles) shell = shell.replace(/<\/head>/i, `${styles}</head>`);
79
79
  return { shell, postponed: rendered.postponed, clientDynamic };
80
80
  } catch (error) {