@rangojs/router 0.33.2 → 0.34.1

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 (56) hide show
  1. package/dist/bin/{chunk-YP4HENIU.js → chunk-4HHFQSDC.js} +6 -1
  2. package/dist/bin/{chunk-3B7CKBRO.js → chunk-MWOHBDPM.js} +9 -9
  3. package/dist/bin/{client-urls-projection-J2BGDKQ4.js → client-urls-projection-4Q5EQDAL.js} +2 -2
  4. package/dist/bin/rango.js +1 -1
  5. package/dist/bin/{runtime-EPUDEHO4.js → runtime-6WLCJCN7.js} +1 -1
  6. package/dist/bin/{runtime-discovery-O6KQ5KO2.js → runtime-discovery-RY6NMQM3.js} +2 -2
  7. package/dist/bin/{temp-rsc-server-BOHXBOR7.js → temp-rsc-server-F7AHOPV4.js} +3 -3
  8. package/dist/types/client-urls/server-projection.d.ts +7 -0
  9. package/dist/types/client-urls/types.d.ts +13 -14
  10. package/dist/types/errors.d.ts +5 -0
  11. package/dist/types/loader-store.d.ts +19 -7
  12. package/dist/types/prefetch-markers.d.ts +13 -4
  13. package/dist/types/route-definition/helpers-types.d.ts +14 -10
  14. package/dist/types/router/match-context.d.ts +12 -0
  15. package/dist/types/router/navigation-snapshot.d.ts +17 -0
  16. package/dist/types/router/segment-resolution/prefetch-deferral.d.ts +39 -21
  17. package/dist/types/server/context.d.ts +6 -5
  18. package/dist/types/testing/serve-shell-request.d.ts +7 -7
  19. package/dist/types/types/loader-types.d.ts +10 -8
  20. package/dist/types/urls/path-helper-types.d.ts +9 -8
  21. package/dist/vite/index.js +26 -12
  22. package/package.json +1 -1
  23. package/skills/app-structure/large-app.md +3 -0
  24. package/skills/client-urls/SKILL.md +13 -10
  25. package/skills/hooks/data.md +4 -1
  26. package/skills/intercept/SKILL.md +8 -4
  27. package/skills/links/SKILL.md +15 -12
  28. package/skills/loader/SKILL.md +52 -39
  29. package/skills/ppr/SKILL.md +1 -1
  30. package/skills/route/SKILL.md +15 -9
  31. package/skills/testing/serve-shell-request.md +19 -7
  32. package/skills/ux-architecture/SKILL.md +16 -11
  33. package/src/browser/navigation-bridge.ts +6 -0
  34. package/src/browser/partial-update.ts +12 -4
  35. package/src/browser/prefetch/fetch.ts +3 -3
  36. package/src/client-urls/client-urls.ts +11 -7
  37. package/src/client-urls/server-projection.ts +21 -3
  38. package/src/client-urls/types.ts +13 -14
  39. package/src/errors.ts +17 -0
  40. package/src/loader-store.ts +28 -12
  41. package/src/prefetch-markers.ts +18 -4
  42. package/src/route-definition/dsl-helpers.ts +13 -6
  43. package/src/route-definition/helpers-types.ts +14 -10
  44. package/src/router/loader-resolution.ts +4 -4
  45. package/src/router/match-api.ts +3 -3
  46. package/src/router/match-context.ts +22 -1
  47. package/src/router/match-middleware/cache-lookup.ts +17 -8
  48. package/src/router/match-middleware/intercept-resolution.ts +3 -3
  49. package/src/router/navigation-snapshot.ts +39 -8
  50. package/src/router/segment-resolution/prefetch-deferral.ts +108 -50
  51. package/src/rsc/rsc-rendering.ts +1 -1
  52. package/src/server/context.ts +6 -5
  53. package/src/testing/serve-shell-request.ts +7 -7
  54. package/src/types/loader-types.ts +10 -8
  55. package/src/urls/path-helper-types.ts +9 -8
  56. package/src/use-loader.tsx +5 -3
@@ -25,7 +25,11 @@ import {
25
25
  type InterceptConfig,
26
26
  type LoaderEntry,
27
27
  } from "../server/context";
28
- import { assertBooleanOption, invariant } from "../errors";
28
+ import {
29
+ assertBooleanOption,
30
+ assertPrefetchOption,
31
+ invariant,
32
+ } from "../errors";
29
33
  import { validateUserRouteName } from "../route-name.js";
30
34
  import { isCachedFunction } from "../cache/taint.js";
31
35
  import { RangoContext } from "../server/context";
@@ -48,6 +52,7 @@ import type {
48
52
  UseItems,
49
53
  } from "../route-types.js";
50
54
  import { isRenderableLoading } from "../types/boundaries.js";
55
+ import { isPrefetchFlag } from "../prefetch-markers.js";
51
56
  import type { RouteHelpers } from "./helpers-types.js";
52
57
  import {
53
58
  resolveHandlerUse,
@@ -922,7 +927,7 @@ const loader: RouteHelpers<any, any>["loader"] = (
922
927
  "loader() ssr",
923
928
  "stream on every render",
924
929
  );
925
- assertBooleanOption(
930
+ assertPrefetchOption(
926
931
  optionsGiven?.prefetch,
927
932
  "loader() prefetch",
928
933
  "run the loader in prefetches",
@@ -938,7 +943,9 @@ const loader: RouteHelpers<any, any>["loader"] = (
938
943
  loader: loaderDef,
939
944
  revalidate: [] as ShouldRevalidateFn<any, any>[],
940
945
  ...(optionsGiven?.ssr === false && { bake: true as const }),
941
- ...(optionsGiven?.prefetch === false && { prefetch: false as const }),
946
+ ...(isPrefetchFlag(optionsGiven?.prefetch) && {
947
+ prefetch: optionsGiven.prefetch,
948
+ }),
942
949
  // The declaring layout's / parallel's / cache()'s scope. A route's (and
943
950
  // an intercept's) loaders have none: they resolve from the route's name,
944
951
  // like the handler.
@@ -997,7 +1004,7 @@ const loading: RouteHelpers<any, any>["loading"] = (component, options) => {
997
1004
  invariant(false, "No parent entry available for loading()");
998
1005
  }
999
1006
 
1000
- assertBooleanOption(
1007
+ assertPrefetchOption(
1001
1008
  options?.prefetch,
1002
1009
  "loading() prefetch",
1003
1010
  "render what the fallback covers in prefetches",
@@ -1018,8 +1025,8 @@ const loading: RouteHelpers<any, any>["loading"] = (component, options) => {
1018
1025
  // `parent.loading`, so the document evaluation (ssr: false) agrees with
1019
1026
  // the navigation one. Last loading() wins, like `loading` itself.
1020
1027
  parent.loadingPrefetch =
1021
- options?.prefetch === false && isRenderableLoading(resolved)
1022
- ? false
1028
+ isPrefetchFlag(options?.prefetch) && isRenderableLoading(resolved)
1029
+ ? options.prefetch
1023
1030
  : undefined;
1024
1031
 
1025
1032
  const name = `$${store.getNextIndex("loading")}`;
@@ -35,6 +35,7 @@ import type {
35
35
  UseItems,
36
36
  } from "../route-types.js";
37
37
  import type { StaticHandlerRef } from "../static-handler.js";
38
+ import type { PrefetchOption } from "../prefetch-markers.js";
38
39
 
39
40
  // Re-export route item types for backward compatibility
40
41
  export type {
@@ -333,15 +334,16 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
333
334
  * loader(RecommendationsLoader), // still streams behind loading()
334
335
  * ```
335
336
  *
336
- * Pass `{ prefetch: false }` to keep the loader out of speculative
337
- * prefetches (viewport, render): such a prefetch skips it and the click
338
- * that adopts it fetches it in one follow-up request; a hover prefetch runs
339
- * it (see {@link LoaderOptions}). Only on a segment the
337
+ * Pass `{ prefetch: false }` to keep the loader out of prefetches: a
338
+ * prefetch skips it and the click that adopts it fetches it in one
339
+ * follow-up request. `{ prefetch: "intent" }` defers it in viewport and
340
+ * render prefetches only (see {@link LoaderOptions}). Only on a segment the
340
341
  * client does not have yet: a loader the client already holds runs in the
341
342
  * prefetch when it revalidates.
342
343
  *
343
344
  * ```typescript
344
345
  * loader(ReviewsLoader, { prefetch: false }),
346
+ * loader(RelatedLoader, { prefetch: "intent" }),
345
347
  * ```
346
348
  *
347
349
  * @param loaderDef - Loader created with createLoader()
@@ -362,16 +364,18 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
362
364
  * // Skip loading on SSR, only show on client navigation
363
365
  * loading(<Skeleton />, { ssr: false })
364
366
  *
365
- * // Keep everything behind this fallback out of viewport and render
366
- * // prefetches (a hover prefetch runs it)
367
+ * // Keep everything behind this fallback out of prefetches
367
368
  * loading(<Skeleton />, { prefetch: false })
369
+ *
370
+ * // Deferred in viewport and render prefetches only
371
+ * loading(<Skeleton />, { prefetch: "intent" })
368
372
  * ```
369
373
  * @param component - The loading UI to show during navigation
370
374
  * @param options - Configuration options
371
375
  * @param options.ssr - If false, skip showing loading on document requests (SSR)
372
- * @param options.prefetch - If false, the flag applies when this entry's
373
- * segment is new to the client. A viewport or render prefetch then skips
374
- * the whole unit (a hover prefetch runs it): the
376
+ * @param options.prefetch - `false` or `"intent"` (see
377
+ * {@link LoaderOptions}); the flag applies when this entry's segment is
378
+ * new to the client. A prefetch then skips the whole unit: the
375
379
  * handler, its loaders, its orphan layouts and parallel slots and, for a
376
380
  * layout, every deeper entry; the click that adopts the prefetch shows
377
381
  * this fallback and fetches them in one follow-up request, which reads
@@ -386,7 +390,7 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
386
390
  */
387
391
  loading: (
388
392
  component: ReactNode | (() => ReactNode),
389
- options?: { ssr?: boolean; prefetch?: boolean },
393
+ options?: { ssr?: boolean; prefetch?: PrefetchOption },
390
394
  ) => LoadingItem;
391
395
  /**
392
396
  * Attach an error boundary to catch errors in this segment and children
@@ -731,17 +731,17 @@ function createLoaderExecutor<TEnv>(
731
731
  );
732
732
  }
733
733
 
734
- // prefetch: false (segment-resolution/prefetch-deferral.ts). A
735
- // deferrable loader runs in a fill request, where its entry is held
734
+ // prefetch: false or "intent" (segment-resolution/prefetch-deferral.ts).
735
+ // A deferrable loader runs in a fill request, where its entry is held
736
736
  // and its handler does not run: there is no render to wait for.
737
737
  // Refused on every request kind, so it is seen on a document load.
738
738
  const deferral = internal._prefetchDeferral;
739
739
  if (deferral?.scope.loaderIds.has(currentLoaderId)) {
740
740
  throw new Error(
741
741
  `ctx.rendered() is not available to loader "${currentLoaderId}": it is deferred in prefetches ` +
742
- `(registered with loader(Def, { prefetch: false }), or behind a loading(fallback, { prefetch: false })), ` +
742
+ `(registered with loader(Def, { prefetch: false }) or { prefetch: "intent" }, or behind a loading(fallback, { prefetch: false }) or { prefetch: "intent" }), ` +
743
743
  `and the fill request that runs it afterwards does not re-run its entry's handler, so there is no render to wait for and no handle data to read. ` +
744
- `Remove { prefetch: false } from the loader (or from the loading() above it), or stop calling ctx.rendered() in this loader.`,
744
+ `Remove { prefetch: false } or { prefetch: "intent" } from the loader (or from the loading() above it), or stop calling ctx.rendered() in this loader.`,
745
745
  );
746
746
  }
747
747
  if (deferral?.mode === "fill") {
@@ -203,6 +203,7 @@ export async function createMatchContextForFull<TEnv>(
203
203
  prevUrl: cleanUrl,
204
204
  prevParams: {},
205
205
  prevMatch: null,
206
+ interceptPrev: { url: cleanUrl, params: {} },
206
207
  matched,
207
208
  manifestEntry: snapshot.manifestEntry,
208
209
  entries: snapshot.entries,
@@ -491,9 +492,7 @@ export async function createMatchContextForPartial<TEnv>(
491
492
  const deferral = planPrefetchDeferral(
492
493
  snapshot.entries,
493
494
  partialDeferralMode({
494
- fill: kind === "fill",
495
- // Speculative only: an intent prefetch defers nothing.
496
- prefetch: kind === "prefetch",
495
+ kind,
497
496
  method: request.method,
498
497
  isAction,
499
498
  isShellCapture: reqCtx?._shellCaptureState?.run === true,
@@ -522,6 +521,7 @@ export async function createMatchContextForPartial<TEnv>(
522
521
  prevUrl: nav.prevUrl,
523
522
  prevParams: nav.prevParams,
524
523
  prevMatch: nav.prevMatch,
524
+ interceptPrev: nav.interceptPrev,
525
525
  matched,
526
526
  manifestEntry: snapshot.manifestEntry,
527
527
  entries: snapshot.entries,
@@ -116,6 +116,7 @@ import type {
116
116
  } from "../server/context.js";
117
117
  import type { HandlerContext, ResolvedSegment } from "../types.js";
118
118
  import type { RouteMatchResult } from "./pattern-matching.js";
119
+ import type { PrevPage } from "./navigation-snapshot.js";
119
120
  import type { InterceptResult } from "./router-context.js";
120
121
  import type { ResolutionPassInput } from "./segment-resolution/resolution-pass.js";
121
122
  import type { ActionContext } from "./types.js";
@@ -139,10 +140,13 @@ export interface MatchContext<TEnv = any> {
139
140
  /** NavigationSnapshot.heldBoundary: the walk stops above it. */
140
141
  heldBoundary?: string;
141
142
 
142
- // Previous navigation state
143
+ // Previous navigation state: see NavigationSnapshot.
143
144
  prevUrl: URL;
144
145
  prevParams: Record<string, string>;
146
+ /** The match of the page left: the intercept's URL while one is open. */
145
147
  prevMatch: RouteMatchResult | null;
148
+ /** The page an open intercept's slot was rendered for. */
149
+ interceptPrev: PrevPage;
146
150
 
147
151
  // Current route match
148
152
  matched: RouteMatchResult;
@@ -225,6 +229,23 @@ export function resolutionPassInput<TEnv>(
225
229
  };
226
230
  }
227
231
 
232
+ /**
233
+ * This match as the input of the pass that resolves its intercept's slot: its
234
+ * held segments compare with the intercept's own URL (`interceptPrev`), not
235
+ * with the source page under it. From /item/2 over /list/1, a link to /item/1
236
+ * compared with the source's params would read as param-equal and keep item
237
+ * 2's slot.
238
+ */
239
+ export function interceptPassInput<TEnv>(
240
+ ctx: MatchContext<TEnv>,
241
+ ): ResolutionPassInput<TEnv> {
242
+ return {
243
+ ...resolutionPassInput(ctx),
244
+ prevUrl: ctx.interceptPrev.url,
245
+ prevParams: ctx.interceptPrev.params,
246
+ };
247
+ }
248
+
228
249
  /**
229
250
  * Mutable state that flows through the pipeline
230
251
  */
@@ -49,6 +49,7 @@ import {
49
49
  type MatchPipelineState,
50
50
  } from "../match-context.js";
51
51
  import { getRouterContext, type RouterContext } from "../router-context.js";
52
+ import type { PrevPage } from "../navigation-snapshot.js";
52
53
  import { freshPassInput } from "../segment-resolution/resolution-pass.js";
53
54
  import { observeEvent } from "../instrument.js";
54
55
  import { pushRevalidationTraceEntry, isTraceActive } from "../logging.js";
@@ -336,7 +337,12 @@ function storedEntrySends<TEnv>(
336
337
  router: RouterContext<TEnv>,
337
338
  ): (segment: ResolvedSegment) => boolean | Promise<boolean> {
338
339
  if (ctx.isFullMatch) return () => true;
339
- const paramsChanged = !paramsEqual(ctx.matched.params, ctx.prevParams);
340
+ // An intercept variant's slot compares with the intercept's own page, the
341
+ // page under it with the source (MatchContext.interceptPrev).
342
+ const pagePrev: PrevPage = { url: ctx.prevUrl, params: ctx.prevParams };
343
+ const slotPrev = ctx.interceptPrev;
344
+ const pageParamsChanged = !paramsEqual(ctx.matched.params, pagePrev.params);
345
+ const slotParamsChanged = !paramsEqual(ctx.matched.params, slotPrev.params);
340
346
  const resendRouteSegments = replaceable && (ctx.isAction || ctx.stale);
341
347
  const entryRevalidateMap =
342
348
  ctx.clientSegmentSet.size > 0
@@ -345,11 +351,11 @@ function storedEntrySends<TEnv>(
345
351
  // A keep that hangs on the source's params is right for the pages with
346
352
  // the same params only: scope a prefetch of the answer to them, as a
347
353
  // predicate that compared every param would (#1331).
348
- const heldForSource = (): false => {
354
+ const heldForSource = (prevParams: Record<string, string>): false => {
349
355
  markSourceHeld(
350
356
  ctx.handlerContext as InternalHandlerContext<any, TEnv>,
351
- readsOfAllParams(ctx.prevParams),
352
- { params: ctx.prevParams, routeName: undefined },
357
+ readsOfAllParams(prevParams),
358
+ { params: prevParams, routeName: undefined },
353
359
  );
354
360
  return false;
355
361
  };
@@ -362,23 +368,26 @@ function storedEntrySends<TEnv>(
362
368
  ctx.clientSegmentSet,
363
369
  );
364
370
  if (fill !== undefined) return fill;
371
+ const ofSlot = isInterceptSegment(segment);
372
+ const prev = ofSlot ? slotPrev : pagePrev;
373
+ const paramsChanged = ofSlot ? slotParamsChanged : pageParamsChanged;
365
374
  if (segment.type === "route" || segment.belongsToRoute === true) {
366
375
  if (paramsChanged || resendRouteSegments) return true;
367
376
  // Kept on equal params, whatever the search: a source with other
368
377
  // params is sent it.
369
- return heldForSource();
378
+ return heldForSource(prev.params);
370
379
  }
371
380
  const revalidate = entryRevalidateMap?.get(segment.id)?.revalidate;
372
381
  if (!revalidate?.length) return false;
373
382
  // Kept on equal params: from another source the rules run and may send
374
383
  // it.
375
- if (!paramsChanged) return heldForSource();
384
+ if (!paramsChanged) return heldForSource(prev.params);
376
385
  return router.evaluateRevalidation({
377
386
  segment,
378
- prevParams: ctx.prevParams,
387
+ prevParams: prev.params,
379
388
  getPrevSegment: null,
380
389
  request: ctx.request,
381
- prevUrl: ctx.prevUrl,
390
+ prevUrl: prev.url,
382
391
  nextUrl: ctx.url,
383
392
  revalidations: revalidate.map((fn, i) => ({
384
393
  name: `revalidate${i}`,
@@ -103,7 +103,7 @@
103
103
  */
104
104
  import type { ResolvedSegment } from "../../types.js";
105
105
  import type { MatchContext, MatchPipelineState } from "../match-context.js";
106
- import { resolutionPassInput } from "../match-context.js";
106
+ import { interceptPassInput } from "../match-context.js";
107
107
  import { getRouterContext } from "../router-context.js";
108
108
  import type { GeneratorMiddleware } from "./cache-lookup.js";
109
109
  import { debugLog } from "../logging.js";
@@ -176,7 +176,7 @@ export function withInterceptResolution<TEnv>(
176
176
  const Store = ctx.Store;
177
177
  const interceptSegments = await Store.run(() =>
178
178
  resolveInterceptEntry(
179
- resolutionPassInput(ctx),
179
+ interceptPassInput(ctx),
180
180
  ctx.interceptResult!.intercept,
181
181
  ctx.interceptResult!.entry,
182
182
  true, // belongsToRoute
@@ -232,7 +232,7 @@ async function handleCacheHitIntercept<TEnv>(
232
232
  const loaderSegments = resolveInterceptLoadersOnly
233
233
  ? await ctx.Store.run(() =>
234
234
  resolveInterceptLoadersOnly(
235
- resolutionPassInput(ctx),
235
+ interceptPassInput(ctx),
236
236
  ctx.interceptResult!.intercept,
237
237
  ctx.interceptResult!.entry,
238
238
  true, // belongsToRoute
@@ -3,12 +3,30 @@ import { requestHeaders } from "../server/request-headers.js";
3
3
  import type { RouteMatchResult } from "./pattern-matching.js";
4
4
  import { WIRE } from "../wire-params.js";
5
5
 
6
+ /** A page held segments were rendered for: what their revalidation compares with. */
7
+ export interface PrevPage {
8
+ url: URL;
9
+ params: Record<string, string>;
10
+ }
11
+
6
12
  export interface NavigationSnapshot {
13
+ /**
14
+ * `prevUrl` and `prevParams`: the page the held page segments were rendered
15
+ * for. That is the page left, or the source page while an intercept is open
16
+ * (`hasInterceptSource`).
17
+ */
7
18
  prevUrl: URL;
8
19
  /** The visitor's URL of the page left, when `prevUrl` is its rewrite target. */
9
20
  prevVisitorUrl: URL | null;
10
21
  prevParams: Record<string, string>;
22
+ /** The match of the page left: the intercept's URL while one is open. */
11
23
  prevMatch: RouteMatchResult | null;
24
+ /**
25
+ * The page an open intercept's slot was rendered for: the intercept's own
26
+ * URL (match-context.ts interceptPassInput). Equals `prevUrl`/`prevParams`
27
+ * when the request names no intercept source.
28
+ */
29
+ interceptPrev: PrevPage;
12
30
 
13
31
  interceptContextUrl: URL;
14
32
  interceptContextMatch: RouteMatchResult | null;
@@ -176,29 +194,40 @@ export async function resolveNavigation(
176
194
  }
177
195
  // An intercept's request diffs from its source, which _rsc_rt does not name.
178
196
  const shown = interceptSourceUrl ? null : getShownRouteUrl(url);
179
- const prevUrl = shown ?? visitor;
197
+ const left = shown ?? visitor;
180
198
 
181
199
  let interceptContextUrl: URL;
182
200
  try {
183
201
  interceptContextUrl = interceptSourceUrl
184
202
  ? withoutHash(new URL(interceptSourceUrl, url.origin))
185
- : prevUrl;
203
+ : left;
186
204
  } catch {
187
- interceptContextUrl = prevUrl;
205
+ interceptContextUrl = left;
188
206
  }
189
207
 
190
- const prevMatch = await deps.findMatch(prevUrl.pathname);
191
- const prevParams = prevMatch?.params || {};
208
+ const prevMatch = await deps.findMatch(left.pathname);
192
209
  const interceptContextMatch = interceptSourceUrl
193
210
  ? await deps.findMatch(interceptContextUrl.pathname)
194
211
  : prevMatch;
212
+ const interceptPrev: PrevPage = {
213
+ url: left,
214
+ params: prevMatch?.params || {},
215
+ };
216
+ // The page under an open intercept was rendered for the source, with its
217
+ // search, not for the intercept's URL.
218
+ const prev: PrevPage = interceptSourceUrl
219
+ ? {
220
+ url: formSearch(new URL(interceptContextUrl)),
221
+ params: interceptContextMatch?.params || {},
222
+ }
223
+ : interceptPrev;
195
224
 
196
225
  const isSameRouteNavigation = !!(
197
226
  interceptContextMatch && interceptContextMatch.routeKey === currentRouteKey
198
227
  );
199
228
 
200
229
  const hasInterceptSource = !!interceptSourceUrl;
201
- const effectiveFromUrl = hasInterceptSource ? interceptContextUrl : prevUrl;
230
+ const effectiveFromUrl = hasInterceptSource ? interceptContextUrl : left;
202
231
  const effectiveFromMatch = hasInterceptSource
203
232
  ? interceptContextMatch
204
233
  : prevMatch;
@@ -212,10 +241,11 @@ export async function resolveNavigation(
212
241
  const clientSegmentSet = new Set(clientSegmentIds);
213
242
 
214
243
  return {
215
- prevUrl,
244
+ prevUrl: prev.url,
216
245
  prevVisitorUrl: shown && visitor,
217
- prevParams,
246
+ prevParams: prev.params,
218
247
  prevMatch,
248
+ interceptPrev,
219
249
  interceptContextUrl,
220
250
  interceptContextMatch,
221
251
  clientSegmentIds,
@@ -240,6 +270,7 @@ export function createNavigationSnapshot(
240
270
  prevVisitorUrl: null,
241
271
  prevParams: {},
242
272
  prevMatch: null,
273
+ interceptPrev: { url: defaultUrl, params: {} },
243
274
  interceptContextUrl: defaultUrl,
244
275
  interceptContextMatch: null,
245
276
  clientSegmentIds: [],