vinext 1.4.1 → 1.4.2

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 (83) hide show
  1. package/README.md +1 -1
  2. package/dist/build/action-owner-manifest.js +1 -0
  3. package/dist/check.js +1 -1
  4. package/dist/config/config-matchers.js +2 -2
  5. package/dist/config/destination-params.js +5 -2
  6. package/dist/config/next-config.d.ts +16 -1
  7. package/dist/config/next-config.js +31 -1
  8. package/dist/entries/app-rsc-entry.js +41 -18
  9. package/dist/entries/app-rsc-manifest.js +4 -0
  10. package/dist/entries/pages-server-entry.js +2 -0
  11. package/dist/index.js +15 -12
  12. package/dist/init-cloudflare.js +18 -2
  13. package/dist/plugins/css-target.d.ts +18 -0
  14. package/dist/plugins/css-target.js +93 -0
  15. package/dist/routing/app-route-graph.d.ts +10 -0
  16. package/dist/routing/app-route-graph.js +15 -1
  17. package/dist/server/api-handler.d.ts +2 -0
  18. package/dist/server/api-handler.js +3 -5
  19. package/dist/server/app-action-forwarding.js +3 -1
  20. package/dist/server/app-browser-entry.js +21 -4
  21. package/dist/server/app-elements.d.ts +3 -0
  22. package/dist/server/app-elements.js +14 -1
  23. package/dist/server/app-fallback-renderer.js +2 -2
  24. package/dist/server/app-optimistic-routing.d.ts +12 -1
  25. package/dist/server/app-optimistic-routing.js +38 -16
  26. package/dist/server/app-page-boundary-render.js +2 -2
  27. package/dist/server/app-page-cache-render.js +1 -1
  28. package/dist/server/app-page-dispatch.d.ts +10 -1
  29. package/dist/server/app-page-dispatch.js +73 -30
  30. package/dist/server/app-page-element-builder.d.ts +9 -20
  31. package/dist/server/app-page-element-builder.js +37 -49
  32. package/dist/server/app-page-intercept-segment-config.d.ts +56 -0
  33. package/dist/server/app-page-intercept-segment-config.js +90 -0
  34. package/dist/server/app-page-navigation-params.d.ts +23 -0
  35. package/dist/server/app-page-navigation-params.js +49 -0
  36. package/dist/server/app-page-render.d.ts +5 -2
  37. package/dist/server/app-page-render.js +17 -3
  38. package/dist/server/app-page-request.d.ts +24 -2
  39. package/dist/server/app-page-request.js +10 -10
  40. package/dist/server/app-page-route-wiring.d.ts +10 -0
  41. package/dist/server/app-page-route-wiring.js +16 -12
  42. package/dist/server/app-page-segment-plan.js +9 -1
  43. package/dist/server/app-page-segment-state.d.ts +9 -0
  44. package/dist/server/app-page-segment-state.js +54 -1
  45. package/dist/server/app-response-stage-entry.js +1 -0
  46. package/dist/server/app-route-module-loader.d.ts +2 -0
  47. package/dist/server/app-route-module-loader.js +1 -0
  48. package/dist/server/app-rsc-handler.d.ts +10 -1
  49. package/dist/server/app-rsc-handler.js +75 -43
  50. package/dist/server/app-rsc-response-finalizer.d.ts +3 -0
  51. package/dist/server/app-rsc-response-finalizer.js +11 -1
  52. package/dist/server/app-rsc-route-matching.d.ts +14 -0
  53. package/dist/server/app-rsc-route-matching.js +42 -18
  54. package/dist/server/app-server-action-execution.js +16 -10
  55. package/dist/server/cache-control.d.ts +2 -0
  56. package/dist/server/cache-control.js +3 -1
  57. package/dist/server/image-local-patterns.d.ts +21 -0
  58. package/dist/server/image-local-patterns.js +59 -0
  59. package/dist/server/image-optimization.d.ts +23 -22
  60. package/dist/server/image-optimization.js +12 -2
  61. package/dist/server/metadata-route-response.js +24 -6
  62. package/dist/server/nitro-public-files-plugin.js +20 -8
  63. package/dist/server/pages-data-route.d.ts +5 -1
  64. package/dist/server/pages-data-route.js +14 -6
  65. package/dist/server/pages-request-pipeline.js +6 -5
  66. package/dist/server/pages-request-stage-entry.js +1 -0
  67. package/dist/server/prod-server.js +17 -9
  68. package/dist/server/request-pipeline.d.ts +5 -0
  69. package/dist/server/request-pipeline.js +11 -1
  70. package/dist/server/response-stage-cacheability.d.ts +7 -0
  71. package/dist/server/response-stage-cacheability.js +6 -5
  72. package/dist/server/worker-image-optimization.d.ts +2 -2
  73. package/dist/server/worker-image-optimization.js +18 -3
  74. package/dist/server/worker-utils.d.ts +8 -0
  75. package/dist/server/worker-utils.js +19 -2
  76. package/dist/shims/image-props.d.ts +6 -0
  77. package/dist/shims/image-props.js +39 -3
  78. package/dist/shims/image.js +3 -3
  79. package/dist/shims/slot.d.ts +3 -0
  80. package/dist/shims/slot.js +67 -9
  81. package/dist/utils/base-path.d.ts +7 -0
  82. package/dist/utils/base-path.js +11 -1
  83. package/package.json +1 -1
@@ -101,6 +101,7 @@ type AppRscRouteForMatching = {
101
101
  pattern: string;
102
102
  patternParts: string[];
103
103
  routeHandler?: unknown;
104
+ routeSegments?: readonly string[] | null;
104
105
  slots?: Record<string, AppRscSlotForMatching>;
105
106
  siblingIntercepts?: AppRscSiblingInterceptForMatching[];
106
107
  };
@@ -108,6 +109,14 @@ type AppRscInterceptMatch = AppRscInterceptLookupEntry & {
108
109
  matchedParams: AppRscRouteParams;
109
110
  sourceRouteIsConcrete: boolean;
110
111
  sourceMatchedParams: AppRscRouteParams;
112
+ /**
113
+ * The source route and params the request's interception context names,
114
+ * which the request handler authorizes. They differ from the rendered
115
+ * `sourceRouteIndex` only for a sibling intercept opened from a deeper
116
+ * source, which renders the intercepting route itself.
117
+ */
118
+ authorizationSourceRouteIndex: number;
119
+ authorizationSourceParams: AppRscRouteParams;
111
120
  };
112
121
  type AppRscInterceptLoadState = {
113
122
  page: unknown;
@@ -120,6 +129,11 @@ type AppRscInterceptLookupEntry = {
120
129
  interceptionId: string | null;
121
130
  interceptionGraphId: string | null;
122
131
  sourceRouteIndex: number;
132
+ /**
133
+ * A sibling intercept whose owner route's page sits in the directory of the
134
+ * interception marker, so the owner is the intercepting route itself.
135
+ */
136
+ ownerIsInterceptingRoute: boolean;
123
137
  slotKey: string;
124
138
  targetPattern: string;
125
139
  targetPatternParts: string[];
@@ -1,7 +1,8 @@
1
- import { decodeMatchedParams, decodeRouteSegment, splitPathSegments, splitPathnameForRouteMatch } from "../routing/utils.js";
1
+ import { decodeMatchedParams, decodeRouteSegment, splitPathSegments } from "../routing/utils.js";
2
2
  import { createAppRouteGraphInterceptionId } from "../routing/app-route-ids.js";
3
3
  import { buildRouteTrie, trieMatchRaw } from "../routing/route-trie.js";
4
4
  import { extractRawRoutePatternParams, matchRoutePatternPrefix, matchRoutePatternRaw } from "../routing/route-pattern.js";
5
+ import { getRenderedConcreteUrlPathsForRoute } from "./pregenerated-concrete-paths.js";
5
6
  import { canonicalizeAppPageParams } from "./app-page-segment-state.js";
6
7
  //#region src/server/app-rsc-route-matching.ts
7
8
  /**
@@ -13,10 +14,19 @@ const SIBLING_PAGE_INTERCEPT_SLOT_KEY = "__vinext_page_intercept";
13
14
  function createRouteParams() {
14
15
  return Object.create(null);
15
16
  }
16
- function appRscPathnameParts(pathname, isNormalized = false) {
17
+ /**
18
+ * Whether each encoded raw segment is exactly the spelling the URL parser, as
19
+ * a browser runs it, gives its decoded form: `caf%C3%A9` for `café`, but `q&a`
20
+ * for `q&a`. `%61dmin`, `caf%c3%a9`, `%63af%C3%A9` and `q%26a` are other
21
+ * spellings of a page, which middleware never compared as its pathname.
22
+ */
23
+ function isCanonicalPathEncoding(rawParts, decodedParts) {
24
+ return rawParts.every((rawPart, index) => !rawPart.includes("%") || rawPart === new URL(`/${decodedParts[index]}`, "http://n").pathname.slice(1));
25
+ }
26
+ function appRscPathnameParts(pathname) {
17
27
  const pathOnly = pathname.split("?")[0];
18
28
  const normalizedPathname = pathOnly === "/" ? "/" : pathOnly.replace(/\/$/, "");
19
- return isNormalized ? splitPathSegments(normalizedPathname) : splitPathnameForRouteMatch(normalizedPathname);
29
+ return splitPathSegments(normalizedPathname);
20
30
  }
21
31
  function isAppRouteHandlerRoute(route) {
22
32
  return route.routeHandler != null || typeof route.__loadRouteHandler === "function";
@@ -49,8 +59,10 @@ function createAppRscRouteMatcher(routes, decodedPathnamePatterns = []) {
49
59
  const result = matchRawRequestParts(rawParts);
50
60
  if (!result) return null;
51
61
  if (decodedPathnameRoutes.size > 0 && result.route.patternParts.some((part) => part.startsWith(":")) && !isAppRouteHandlerRoute(result.route) && rawParts.some((part) => part.includes("%"))) {
52
- const route = decodedPathnameRoutes.get(rawParts.map(decodeRouteSegment).join("/"));
53
- if (route && route !== result.route) return {
62
+ const decodedParts = rawParts.map(decodeRouteSegment);
63
+ const decodedPathname = decodedParts.join("/");
64
+ const route = decodedPathnameRoutes.get(decodedPathname);
65
+ if (route && route !== result.route && (isCanonicalPathEncoding(rawParts, decodedParts) || getRenderedConcreteUrlPathsForRoute(route.pattern)?.has(`/${decodedPathname}`) === true)) return {
54
66
  route,
55
67
  params: createRouteParams()
56
68
  };
@@ -62,23 +74,18 @@ function createAppRscRouteMatcher(routes, decodedPathnamePatterns = []) {
62
74
  return interceptionIds.has(interceptionId);
63
75
  },
64
76
  matchRoute(url) {
65
- const rawParts = appRscPathnameParts(url, true);
66
- const result = trieMatchRaw(routeTrie, appRscPathnameParts(url, false));
67
- if (!result) return null;
68
- result.params = extractRawRoutePatternParams(result.route.patternParts, rawParts);
69
- normalizeMatchedParamsForRoute(result);
70
- return result;
77
+ return matchRequestParts(appRscPathnameParts(url));
71
78
  },
72
79
  matchRequestRoute(url) {
73
- return matchRequestParts(appRscPathnameParts(url, true));
80
+ return matchRequestParts(appRscPathnameParts(url));
74
81
  },
75
82
  matchRawRequestRoute(url) {
76
- return matchRawRequestParts(appRscPathnameParts(url, true));
83
+ return matchRawRequestParts(appRscPathnameParts(url));
77
84
  },
78
85
  findIntercept(pathname, sourcePathname = null, interceptionId = null) {
79
86
  if (sourcePathname === null) return null;
80
- const urlParts = appRscPathnameParts(pathname, true);
81
- const sourceParts = appRscPathnameParts(sourcePathname, true);
87
+ const urlParts = appRscPathnameParts(pathname);
88
+ const sourceParts = appRscPathnameParts(sourcePathname);
82
89
  const matchedSourceRoute = matchRawRequestParts(sourceParts);
83
90
  for (const entry of interceptLookup) {
84
91
  if (interceptionId !== null && entry.interceptionId !== interceptionId) continue;
@@ -92,15 +99,21 @@ function createAppRscRouteMatcher(routes, decodedPathnamePatterns = []) {
92
99
  if (sourceRoute && isAppRouteHandlerRoute(sourceRoute)) continue;
93
100
  const matchedSourceParams = concreteSourceRoute ? concreteSourceRoute.params : sourceRoute ? matchSlotOwnerSourceParams(sourceParts, sourceRoute.patternParts, entry.sourceMatchPatternParts !== null) : null;
94
101
  if (!concreteSourceRoute && matchedSourceParams) canonicalizeAppPageParams(matchedSourceParams);
102
+ const ownerRoute = routes[entry.sourceRouteIndex];
103
+ const rendersOwnerRoute = entry.ownerIsInterceptingRoute && concreteSourceRouteIndex !== entry.sourceRouteIndex && ownerRoute !== void 0;
104
+ const renderedSourceParams = rendersOwnerRoute ? matchSlotOwnerSourceParams(sourceParts, ownerRoute.patternParts, true) : matchedSourceParams;
105
+ if (rendersOwnerRoute && renderedSourceParams) canonicalizeAppPageParams(renderedSourceParams);
95
106
  if (matchedSourceParams === null && entry.sourceMatchPatternParts === null) continue;
96
- const sourceParams = matchedSourceParams && entry.sourceMatchPatternParts !== null ? pickPatternParams(matchedSourceParams, entry.sourceMatchPatternParts) : matchedSourceParams ?? createRouteParams();
107
+ const sourceParams = renderedSourceParams && entry.sourceMatchPatternParts !== null ? pickPatternParams(renderedSourceParams, entry.sourceMatchPatternParts) : renderedSourceParams ?? createRouteParams();
97
108
  return {
98
109
  ...entry,
99
110
  page: entry.__loadState.page,
100
111
  sourceRouteIsConcrete: concreteSourceRoute !== null,
101
- sourceRouteIndex: concreteSourceRouteIndex,
112
+ sourceRouteIndex: rendersOwnerRoute ? entry.sourceRouteIndex : concreteSourceRouteIndex,
102
113
  matchedParams: mergeMatchedParams(sourceParams, params),
103
- sourceMatchedParams: matchedSourceParams ?? createRouteParams()
114
+ sourceMatchedParams: renderedSourceParams ?? createRouteParams(),
115
+ authorizationSourceRouteIndex: concreteSourceRouteIndex,
116
+ authorizationSourceParams: matchedSourceParams ?? createRouteParams()
104
117
  };
105
118
  }
106
119
  return null;
@@ -108,6 +121,15 @@ function createAppRscRouteMatcher(routes, decodedPathnamePatterns = []) {
108
121
  };
109
122
  }
110
123
  /**
124
+ * Whether a sibling intercept's owner route is the intercepting route: its
125
+ * page lives in the directory that holds the interception marker. An owner
126
+ * found elsewhere stands in for a marker directory without a page.
127
+ */
128
+ function isInterceptingRoute(routeSegments, sourcePageSegments) {
129
+ if (!routeSegments || !sourcePageSegments) return false;
130
+ return sourcePageSegments.findIndex((segment) => /^\(\.+\)/.test(segment)) === routeSegments.length && routeSegments.every((segment, index) => segment === sourcePageSegments[index]);
131
+ }
132
+ /**
111
133
  * Params for the slot owner when interception falls back to it instead of a
112
134
  * concrete descendant source route. The owner is what renders, and
113
135
  * `matchInterceptRoute` reads the promoted route's params solely from these,
@@ -195,6 +217,7 @@ function createInterceptLookup(routes) {
195
217
  interceptionId: typeof slotModule.id === "string" && sourceMatchPattern !== null ? createAppRouteGraphInterceptionId(slotModule.id, sourceMatchPattern, intercept.targetPattern) : null,
196
218
  interceptionGraphId: null,
197
219
  sourceRouteIndex: ownerRouteIndex,
220
+ ownerIsInterceptingRoute: false,
198
221
  slotKey,
199
222
  slotId: typeof slotModule.id === "string" ? slotModule.id : null,
200
223
  targetRouteGraphId: resolveTargetRouteGraphId(intercept.targetPattern),
@@ -246,6 +269,7 @@ function createInterceptLookup(routes) {
246
269
  interceptionId: typeof intercept.id === "string" ? intercept.id : null,
247
270
  interceptionGraphId: typeof intercept.id === "string" ? intercept.id : null,
248
271
  sourceRouteIndex: routeIndex,
272
+ ownerIsInterceptingRoute: !isAppRouteHandlerRoute(route) && isInterceptingRoute(route.routeSegments, intercept.sourcePageSegments),
249
273
  slotKey: SIBLING_PAGE_INTERCEPT_SLOT_KEY,
250
274
  slotId: typeof intercept.slotId === "string" ? intercept.slotId : null,
251
275
  targetRouteGraphId: resolveTargetRouteGraphId(intercept.targetPattern),
@@ -12,6 +12,7 @@ import { attachRequestCfMetadata, validateCsrfOrigin, validateServerActionPayloa
12
12
  import { splitPathSegments } from "../routing/utils.js";
13
13
  import { getRequestExecutionContext } from "../shims/request-context.js";
14
14
  import { logRequestError } from "./request-error-log.js";
15
+ import { SERVER_ACTION_CACHE_CONTROL, applyCdnResponseHeaders } from "./cache-control.js";
15
16
  import { shareIsolatePromise } from "./isolate-shared-promise.js";
16
17
  import { APP_RSC_RENDER_MODE_NAVIGATION } from "./app-rsc-render-mode.js";
17
18
  import { isDraftModeRequest, setHeadersContext } from "../shims/headers.js";
@@ -21,17 +22,18 @@ import { readStreamAsTextWithLimit } from "../utils/text-stream.js";
21
22
  import { mergeMiddlewareResponseHeaders } from "./middleware-response-headers.js";
22
23
  import { VINEXT_RSC_CONTENT_TYPE, applyRscCompatibilityIdHeader, createRscRequestUrl, stripRscCacheBustingSearchParam } from "./app-rsc-cache-busting.js";
23
24
  import { getSetCookieName } from "./cookie-utils.js";
25
+ import { markAppRscResponseConfigHeadersApplied, markStreamedActionRedirectResponse } from "./app-rsc-response-finalizer.js";
24
26
  import { createServerActionNotFoundResponse, getServerActionNotFoundMessage, isServerActionNotFoundError } from "./server-action-not-found.js";
25
27
  import { applyEdgeRuntimeHeader } from "./app-page-response.js";
26
28
  import { getNextErrorDigest, parseNextHttpErrorDigest, parseNextRedirectDigest } from "./next-error-digest.js";
27
- import { hasDigest } from "./app-rsc-errors.js";
28
- import { resolveAppPageNavigationParams } from "./app-page-element-builder.js";
29
+ import { resolveAppPageNavigationParams } from "./app-page-navigation-params.js";
29
30
  import "./app-page-stream.js";
31
+ import { hasDigest } from "./app-rsc-errors.js";
30
32
  import { buildAppPageTags } from "./implicit-tags.js";
33
+ import { resolveAppPageInterceptSegmentConfig } from "./app-page-intercept-segment-config.js";
31
34
  import { resolveAppPageActionRerenderTarget } from "./app-page-request.js";
32
35
  import { createRootParamsUsageController, runWithRootParamsUsage } from "../shims/root-params.js";
33
36
  import { createStaticGenerationHeadersContext } from "./app-static-generation.js";
34
- import { markAppRscResponseConfigHeadersApplied } from "./app-rsc-response-finalizer.js";
35
37
  //#region src/server/app-server-action-execution.ts
36
38
  /**
37
39
  * Run a server-reference load or decode outside every request scope.
@@ -659,7 +661,7 @@ async function handleProgressiveServerActionRequest(options) {
659
661
  options.clearRequestContext();
660
662
  const headers = new Headers();
661
663
  mergeMiddlewareResponseHeaders(headers, options.middlewareHeaders);
662
- headers.set("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate");
664
+ headers.set("Cache-Control", SERVER_ACTION_CACHE_CONTROL);
663
665
  for (const cookie of actionPendingCookies) headers.append("Set-Cookie", cookie);
664
666
  if (actionDraftCookie) headers.append("Set-Cookie", actionDraftCookie);
665
667
  return internalServerErrorResponse(process.env.NODE_ENV === "production" ? void 0 : "Server action failed: " + getServerActionFailureMessage(actionError), { headers });
@@ -879,6 +881,7 @@ async function handleServerActionRscRequest(options) {
879
881
  applyEdgeRuntimeHeader(redirectHeaders, options.isEdgeRuntime);
880
882
  mergeActionRedirectSourceHeaders(redirectHeaders, options.sourceConfigHeaders ?? null);
881
883
  mergeActionRedirectSourceHeaders(redirectHeaders, options.middlewareHeaders);
884
+ applyCdnResponseHeaders(redirectHeaders, { cacheControl: SERVER_ACTION_CACHE_CONTROL });
882
885
  applyRscCompatibilityIdHeader(redirectHeaders);
883
886
  const actionRedirectUrl = applyActionRedirectBasePath(actionRedirect.url, options.basePath ?? "");
884
887
  redirectHeaders.set(ACTION_REDIRECT_HEADER, actionRedirectUrl);
@@ -938,10 +941,10 @@ async function handleServerActionRscRequest(options) {
938
941
  targetPathname,
939
942
  targetRoute: targetMatch?.route ?? null
940
943
  }) ? 200 : 303;
941
- return markAppRscResponseConfigHeadersApplied(createServerActionRscResponse(targetResponse.body, {
944
+ return markStreamedActionRedirectResponse(markAppRscResponseConfigHeadersApplied(createServerActionRscResponse(targetResponse.body, {
942
945
  status: redirectResponseStatus,
943
946
  headers: redirectHeaders
944
- }, options.clearRequestContext));
947
+ }, options.clearRequestContext)));
945
948
  }
946
949
  const actionPendingCookies = dedupePendingCookies(options.getAndClearPendingCookies());
947
950
  const actionDraftCookie = options.getDraftModeCookieHeader();
@@ -976,6 +979,7 @@ async function handleServerActionRscRequest(options) {
976
979
  let element;
977
980
  let errorPattern = match ? match.route.pattern : options.cleanPathname;
978
981
  let renderedPathAndSearch = null;
982
+ let actionRerenderIsEdgeRuntime = options.isEdgeRuntime;
979
983
  const actionRerenderIsRscRequest = true;
980
984
  if (match) {
981
985
  const { route: actionRoute, params: actionParams } = match;
@@ -991,7 +995,9 @@ async function handleServerActionRscRequest(options) {
991
995
  });
992
996
  const resolvedActionNavigationParams = resolveAppPageNavigationParams(actionRerenderTarget.route, actionRerenderTarget.navigationParams, options.cleanPathname, actionRerenderTarget.interceptOpts);
993
997
  await options.ensureRouteLoaded?.(actionRerenderTarget.route);
994
- const actionRerenderDynamicConfig = options.resolveRouteDynamicConfig?.(actionRerenderTarget.route);
998
+ const actionInterceptConfig = actionRerenderTarget.interceptOpts ? resolveAppPageInterceptSegmentConfig(actionRerenderTarget.route, actionRerenderTarget.interceptOpts) : null;
999
+ if (actionInterceptConfig) actionRerenderIsEdgeRuntime = actionInterceptConfig.isEdgeRuntime;
1000
+ const actionRerenderDynamicConfig = actionInterceptConfig ? actionInterceptConfig.dynamicConfig : options.resolveRouteDynamicConfig?.(actionRerenderTarget.route);
995
1001
  const actionRerenderSearchParams = prepareActionPageRerenderContext({
996
1002
  draftModeCookie: actionDraftCookie,
997
1003
  draftModeSecret: options.draftModeSecret,
@@ -1007,8 +1013,8 @@ async function handleServerActionRscRequest(options) {
1007
1013
  });
1008
1014
  const renderedSearch = options.searchParams.toString();
1009
1015
  renderedPathAndSearch = renderedSearch ? `${options.cleanPathname}?${renderedSearch}` : options.cleanPathname;
1010
- setCurrentFetchCacheMode(options.resolveRouteFetchCacheMode?.(actionRerenderTarget.route) ?? null);
1011
- setCurrentFetchRevalidate(options.resolveRouteRevalidateSeconds?.(actionRerenderTarget.route) ?? null);
1016
+ setCurrentFetchCacheMode(actionInterceptConfig ? actionInterceptConfig.fetchCacheMode : options.resolveRouteFetchCacheMode?.(actionRerenderTarget.route) ?? null);
1017
+ setCurrentFetchRevalidate(actionInterceptConfig ? actionInterceptConfig.revalidateSeconds : options.resolveRouteRevalidateSeconds?.(actionRerenderTarget.route) ?? null);
1012
1018
  setCurrentForceDynamicFetchDefault(actionRerenderDynamicConfig === "force-dynamic");
1013
1019
  setCurrentFetchSoftTags(buildServerActionPageTags(actionRerenderTarget.route, options.cleanPathname));
1014
1020
  const buildActionRerenderElement = () => options.buildPageElement({
@@ -1047,7 +1053,7 @@ async function handleServerActionRscRequest(options) {
1047
1053
  "Content-Type": VINEXT_RSC_CONTENT_TYPE,
1048
1054
  Vary: VINEXT_RSC_VARY_HEADER
1049
1055
  });
1050
- applyEdgeRuntimeHeader(actionHeaders, options.isEdgeRuntime);
1056
+ applyEdgeRuntimeHeader(actionHeaders, actionRerenderIsEdgeRuntime);
1051
1057
  mergeMiddlewareResponseHeaders(actionHeaders, options.middlewareHeaders);
1052
1058
  applyRscCompatibilityIdHeader(actionHeaders);
1053
1059
  setActionRevalidatedHeader(actionHeaders, actionRevalidationKind);
@@ -1,6 +1,8 @@
1
1
  import { CdnCacheableHeaderInput, isNonCacheableCacheControl } from "../shims/cdn-cache.js";
2
2
  //#region src/server/cache-control.d.ts
3
3
  export declare const NEVER_CACHE_CONTROL = "private, no-cache, no-store, max-age=0, must-revalidate";
4
+ /** The policy Next.js sends on every Server Action response. */
5
+ export declare const SERVER_ACTION_CACHE_CONTROL = "no-cache, no-store, max-age=0, must-revalidate";
4
6
  export declare const BROWSER_REVALIDATE_CACHE_CONTROL = "public, max-age=0, must-revalidate";
5
7
  export declare const STATIC_CACHE_CONTROL = "s-maxage=31536000, stale-while-revalidate";
6
8
  export declare const NO_STORE_CACHE_CONTROL = "no-store, must-revalidate";
@@ -2,6 +2,8 @@ import { getCdnCacheAdapter, isNonCacheableCacheControl } from "../shims/cdn-cac
2
2
  import { recordRouteCacheabilityCdnTags } from "../shims/cacheability-classification.js";
3
3
  //#region src/server/cache-control.ts
4
4
  const NEVER_CACHE_CONTROL = "private, no-cache, no-store, max-age=0, must-revalidate";
5
+ /** The policy Next.js sends on every Server Action response. */
6
+ const SERVER_ACTION_CACHE_CONTROL = "no-cache, no-store, max-age=0, must-revalidate";
5
7
  const BROWSER_REVALIDATE_CACHE_CONTROL = "public, max-age=0, must-revalidate";
6
8
  const STATIC_CACHE_CONTROL = "s-maxage=31536000, stale-while-revalidate";
7
9
  const STALE_REVALIDATE_CACHE_CONTROL = "s-maxage=0, stale-while-revalidate";
@@ -167,4 +169,4 @@ function buildCachedRevalidateCacheControl(cacheState, revalidateSeconds, expire
167
169
  return buildRevalidateCacheControl(revalidateSeconds, expireSeconds);
168
170
  }
169
171
  //#endregion
170
- export { BROWSER_REVALIDATE_CACHE_CONTROL, NEVER_CACHE_CONTROL, NO_STORE_CACHE_CONTROL, STATIC_CACHE_CONTROL, applyCdnResponseBuildIdentityHeaders, applyCdnResponseHeaders, applyCdnResponseIdentityHeaders, buildCachedRevalidateCacheControl, buildRevalidateCacheControl, captureCdnResponsePolicyHeaders, captureCdnResponsePolicyOverrides, hasCdnResponsePolicy, hasExplicitNonCacheableResponsePolicy, isCdnResponsePolicyHeader, isNonCacheableCacheControl, isNonCacheableCdnResponsePolicy, readCdnResponseCacheControl, readCdnResponsePolicyHeaderName, reconcileCdnResponseHeadersAfterOuterPolicy, shouldUseNextDeployCacheControl, validateCdnRequest };
172
+ export { BROWSER_REVALIDATE_CACHE_CONTROL, NEVER_CACHE_CONTROL, NO_STORE_CACHE_CONTROL, SERVER_ACTION_CACHE_CONTROL, STATIC_CACHE_CONTROL, applyCdnResponseBuildIdentityHeaders, applyCdnResponseHeaders, applyCdnResponseIdentityHeaders, buildCachedRevalidateCacheControl, buildRevalidateCacheControl, captureCdnResponsePolicyHeaders, captureCdnResponsePolicyOverrides, hasCdnResponsePolicy, hasExplicitNonCacheableResponsePolicy, isCdnResponsePolicyHeader, isNonCacheableCacheControl, isNonCacheableCdnResponsePolicy, readCdnResponseCacheControl, readCdnResponsePolicyHeaderName, reconcileCdnResponseHeadersAfterOuterPolicy, shouldUseNextDeployCacheControl, validateCdnRequest };
@@ -0,0 +1,21 @@
1
+ //#region src/server/image-local-patterns.d.ts
2
+ /**
3
+ * `images.localPatterns` matching for the image optimization endpoint, kept
4
+ * apart from the remote-pattern shim so the request path doesn't load its IP
5
+ * parsing.
6
+ */
7
+ export type LocalPattern = {
8
+ pathname?: string;
9
+ search?: string;
10
+ };
11
+ /**
12
+ * Check whether a local URL matches a single local pattern.
13
+ * Follows the same semantics as Next.js's matchLocalPattern().
14
+ */
15
+ export declare function matchLocalPattern(pattern: LocalPattern, url: URL): boolean;
16
+ /**
17
+ * Check whether a local URL matches any configured local pattern. Like
18
+ * Next.js's hasLocalMatch(), an unset list allows every local URL.
19
+ */
20
+ export declare function hasLocalMatch(localPatterns: LocalPattern[] | undefined, url: URL): boolean;
21
+ //#endregion
@@ -0,0 +1,59 @@
1
+ //#region src/server/image-local-patterns.ts
2
+ const localPathnameRegexCache = /* @__PURE__ */ new Map();
3
+ /**
4
+ * Convert a local pathname glob to a RegExp, reading `*` and `**` like Next.js's
5
+ * picomatch (`makeRe(pathname, { dot: true })`):
6
+ * - a `**` segment matches zero or more whole segments, but needs its own
7
+ * separator right after a leading `/`, and at the end after a segment
8
+ * that ends in `*`;
9
+ * - any other `*` run matches characters within one segment;
10
+ * - a last segment ending in `*` also matches one trailing slash, and a
11
+ * whole last `*` matches one character or more (that slash included).
12
+ * Other picomatch syntax (braces, `?`, classes) is matched literally.
13
+ */
14
+ function localPathnameGlobToRegex(pattern) {
15
+ const cached = localPathnameRegexCache.get(pattern);
16
+ if (cached !== void 0) return cached;
17
+ const segments = pattern.split("/").filter((segment, i, all) => !(segment === "**" && all[i - 1] === "**"));
18
+ let regexStr = "^";
19
+ for (let i = 0; i < segments.length; i++) {
20
+ const segment = segments[i];
21
+ const previous = segments[i - 1];
22
+ const isLast = i === segments.length - 1;
23
+ if (segment === "**") {
24
+ if (i === 0) regexStr += isLast ? ".*" : "(?:.*/)?";
25
+ else if (previous === "" || isLast && previous.endsWith("*")) regexStr += "/.*";
26
+ else regexStr += "(?:/.*)?";
27
+ continue;
28
+ }
29
+ if (i > 0 && !(i === 1 && previous === "**")) regexStr += "/";
30
+ if (segment === "*" && isLast) {
31
+ regexStr += "(?:[^/]+/?|/)";
32
+ continue;
33
+ }
34
+ regexStr += segment.split(/\*+/).map((literal) => literal.replace(/[.+?^${}()|[\]\\]/g, "\\$&")).join("[^/]*");
35
+ if (isLast && segment.endsWith("*")) regexStr += "/?";
36
+ }
37
+ regexStr += "$";
38
+ const re = new RegExp(regexStr);
39
+ localPathnameRegexCache.set(pattern, re);
40
+ return re;
41
+ }
42
+ /**
43
+ * Check whether a local URL matches a single local pattern.
44
+ * Follows the same semantics as Next.js's matchLocalPattern().
45
+ */
46
+ function matchLocalPattern(pattern, url) {
47
+ if (pattern.search !== void 0 && pattern.search !== url.search) return false;
48
+ return localPathnameGlobToRegex(pattern.pathname ?? "**").test(url.pathname);
49
+ }
50
+ /**
51
+ * Check whether a local URL matches any configured local pattern. Like
52
+ * Next.js's hasLocalMatch(), an unset list allows every local URL.
53
+ */
54
+ function hasLocalMatch(localPatterns, url) {
55
+ if (!localPatterns) return true;
56
+ return localPatterns.some((p) => matchLocalPattern(p, url));
57
+ }
58
+ //#endregion
59
+ export { hasLocalMatch, matchLocalPattern };
@@ -1,22 +1,5 @@
1
+ import { LocalPattern } from "./image-local-patterns.js";
1
2
  //#region src/server/image-optimization.d.ts
2
- /**
3
- * Image optimization request handler.
4
- *
5
- * Handles `/_next/image?url=...&w=...&q=...` requests. In production
6
- * on Cloudflare Workers, uses the Images binding (`env.IMAGES`) to
7
- * resize and transcode on the fly. On other runtimes (Node.js dev/prod
8
- * server), serves the original file as a passthrough with appropriate
9
- * Cache-Control headers.
10
- *
11
- * Format negotiation: inspects the `Accept` header and serves AVIF, WebP,
12
- * or JPEG depending on client support.
13
- *
14
- * Security: All image responses include Content-Security-Policy and
15
- * X-Content-Type-Options headers to prevent XSS via SVG or Content-Type
16
- * spoofing. SVG content is blocked by default (following Next.js behavior).
17
- * When `dangerouslyAllowSVG` is enabled in next.config.js, SVGs are served
18
- * as-is (no transformation) with security headers applied.
19
- */
20
3
  /** The pathname that triggers image optimization (matches Next.js). */
21
4
  export declare const IMAGE_OPTIMIZATION_PATH = "/_next/image";
22
5
  /**
@@ -49,11 +32,22 @@ export type ImageConfig = {
49
32
  /** Allowed fixed-image widths. Defaults to Next.js image sizes. */
50
33
  imageSizes?: number[];
51
34
  /**
52
- * Allowed output qualities. When unset, any quality from 1-100 is permitted
53
- * (matches Next.js: an unset `images.qualities` is not restricted to a single
54
- * value). When set, only the listed qualities are accepted.
35
+ * Allowed output qualities. When set, only the listed qualities are
36
+ * accepted, as in Next.js. When unset, any quality from 1-100 is accepted.
37
+ *
38
+ * Intentional divergence from Next.js 16, which defaults this to `[75]`:
39
+ * vinext keeps the Next.js 14/15 behaviour for an unset list so apps that
40
+ * adopt vinext from those versions and pass `quality={N}` keep working
41
+ * instead of getting 400s. Apps opt into Next.js 16's strict list by
42
+ * configuring `images.qualities`.
55
43
  */
56
44
  qualities?: number[];
45
+ /**
46
+ * Local sources the endpoint may serve, resolved like Next.js: an app list
47
+ * gains the static-import pattern, and an unset list becomes
48
+ * DEFAULT_LOCAL_PATTERNS when checked.
49
+ */
50
+ localPatterns?: LocalPattern[];
57
51
  /** Allow SVG through the image optimization endpoint. Default: false. */
58
52
  dangerouslyAllowSVG?: boolean;
59
53
  /**
@@ -80,10 +74,12 @@ export declare const DEFAULT_DEVICE_SIZES: number[];
80
74
  export declare const DEFAULT_IMAGE_SIZES: number[];
81
75
  export type ParseImageParamsOptions = {
82
76
  isDev?: boolean;
77
+ /** `images.localPatterns`; DEFAULT_LOCAL_PATTERNS when unset. */
78
+ localPatterns?: LocalPattern[];
83
79
  };
84
80
  export declare function resolveDevImageRedirect(requestUrl: URL, allowedWidths?: number[], allowedQualities?: number[], options?: ParseImageParamsOptions): string | null;
85
81
  export type ImageOptimizationSignal = {
86
- /** Middleware response headers to send with the image. */
82
+ /** next.config and middleware response headers to send with the image. */
87
83
  middlewareHeaders: Headers;
88
84
  /** The request headers after middleware's overrides, when it set any. */
89
85
  requestHeaders: Headers | null;
@@ -120,6 +116,11 @@ export declare function isImageSourceBodyStatus(status: number): boolean;
120
116
  */
121
117
  export declare const INVALID_IMAGE_SOURCE_MESSAGE = "The requested resource isn't a valid image.";
122
118
  export declare function invalidImageSourceResponse(): Response;
119
+ /**
120
+ * Next.js applies this when `images.localPatterns` is unset: every local path,
121
+ * but no query string.
122
+ */
123
+ export declare const DEFAULT_LOCAL_PATTERNS: LocalPattern[];
123
124
  /**
124
125
  * Parse and validate image optimization query parameters.
125
126
  * Returns null if the request is malformed.
@@ -1,5 +1,6 @@
1
1
  import { badRequestResponse } from "./http-error-responses.js";
2
2
  import { setCacheStateHeaders } from "./cache-headers.js";
3
+ import { hasLocalMatch } from "./image-local-patterns.js";
3
4
  //#region src/server/image-optimization.ts
4
5
  /**
5
6
  * Image optimization request handler.
@@ -166,6 +167,14 @@ function invalidImageSourceResponse() {
166
167
  }
167
168
  const RECURSIVE_IMAGE_URL_RE = /\/_(?:next|vinext)\/image(?:$|\/)/;
168
169
  /**
170
+ * Next.js applies this when `images.localPatterns` is unset: every local path,
171
+ * but no query string.
172
+ */
173
+ const DEFAULT_LOCAL_PATTERNS = [{
174
+ pathname: "**",
175
+ search: ""
176
+ }];
177
+ /**
169
178
  * Parse and validate image optimization query parameters.
170
179
  * Returns null if the request is malformed.
171
180
  *
@@ -204,6 +213,7 @@ function parseImageParams(url, allowedWidths = [...DEFAULT_DEVICE_SIZES, ...DEFA
204
213
  if (resolved.origin !== base) return null;
205
214
  if (resolved.pathname.startsWith("//")) return null;
206
215
  if (RECURSIVE_IMAGE_URL_RE.test(decodeURIComponent(resolved.pathname))) return null;
216
+ if (!options.isDev && !hasLocalMatch(options.localPatterns ?? DEFAULT_LOCAL_PATTERNS, resolved)) return null;
207
217
  } catch {
208
218
  return null;
209
219
  }
@@ -317,7 +327,7 @@ function createPassthroughImageResponse(source, config) {
317
327
  * cache headers.
318
328
  */
319
329
  async function handleImageOptimization(request, handlers, allowedWidths, imageConfig) {
320
- const params = parseImageParams(new URL(request.url), allowedWidths, imageConfig?.qualities);
330
+ const params = parseImageParams(new URL(request.url), allowedWidths, imageConfig?.qualities, { localPatterns: imageConfig?.localPatterns });
321
331
  if (!params) return badRequestResponse();
322
332
  const { imageUrl, width, quality } = params;
323
333
  const source = await handlers.fetchAsset(imageUrl, request);
@@ -402,4 +412,4 @@ function handleConfiguredImageOptimization(request, fetchAsset, allowedWidths, i
402
412
  }, allowedWidths, imageConfig);
403
413
  }
404
414
  //#endregion
405
- export { DEFAULT_DEVICE_SIZES, DEFAULT_IMAGE_SIZES, IMAGE_CACHE_CONTROL, IMAGE_CONTENT_SECURITY_POLICY, IMAGE_OPTIMIZATION_PATH, IMAGE_RESPONSE_CACHE_STATE, INVALID_IMAGE_SOURCE_MESSAGE, MUTABLE_IMAGE_SOURCE_CACHE_CONTROL, VINEXT_IMAGE_OPTIMIZATION_PATH, getImageOptimizer, handleConfiguredImageOptimization, handleImageOptimization, invalidImageSourceResponse, isImageOptimizationPath, isImageSourceBodyStatus, isSafeImageContentType, markImageOptimizationSignal, negotiateImageFormat, parseImageParams, readImageOptimizationSignal, resolveDevImageRedirect, restoreImageOptimizationSignalFromTransport, serializeImageOptimizationSignalForTransport, setImageOptimizer };
415
+ export { DEFAULT_DEVICE_SIZES, DEFAULT_IMAGE_SIZES, DEFAULT_LOCAL_PATTERNS, IMAGE_CACHE_CONTROL, IMAGE_CONTENT_SECURITY_POLICY, IMAGE_OPTIMIZATION_PATH, IMAGE_RESPONSE_CACHE_STATE, INVALID_IMAGE_SOURCE_MESSAGE, MUTABLE_IMAGE_SOURCE_CACHE_CONTROL, VINEXT_IMAGE_OPTIMIZATION_PATH, getImageOptimizer, handleConfiguredImageOptimization, handleImageOptimization, invalidImageSourceResponse, isImageOptimizationPath, isImageSourceBodyStatus, isSafeImageContentType, markImageOptimizationSignal, negotiateImageFormat, parseImageParams, readImageOptimizationSignal, resolveDevImageRedirect, restoreImageOptimizationSignalFromTransport, serializeImageOptimizationSignalForTransport, setImageOptimizer };
@@ -504,20 +504,34 @@ async function writeMetadataRouteMiss(options, route, rendered, cachedEntry) {
504
504
  async function handleMetadataRouteRequest(options) {
505
505
  let urlParts;
506
506
  const getUrlParts = () => urlParts ??= options.cleanPathname.split("/").filter(Boolean);
507
+ const routePathname = options.routePathname ?? options.cleanPathname;
508
+ let routeUrlParts;
509
+ const getRouteUrlParts = () => routeUrlParts ??= routePathname.split("/").filter(Boolean);
510
+ let cacheOptions = options;
511
+ const skipDecodedOwner = () => {
512
+ cacheOptions = {
513
+ ...options,
514
+ isrGet: void 0,
515
+ isrRouteKey: void 0,
516
+ isrSet: void 0
517
+ };
518
+ };
507
519
  for (const route of options.metadataRoutes) {
508
520
  const functions = getMetadataRouteFunctions(route);
509
521
  if (route.type === "sitemap" && route.isDynamic) {
510
522
  if (functions.generateSitemaps) {
511
- if (isGeneratedSitemapPath(route, options.cleanPathname)) {
523
+ if (!isGeneratedSitemapPath(route, options.cleanPathname)) continue;
524
+ if (!isGeneratedSitemapPath(route, routePathname)) skipDecodedOwner();
525
+ else {
512
526
  beginMetadataRouteCacheability(route);
513
527
  const render = async () => {
514
528
  setCurrentFetchSoftTags(buildMetadataRouteTags(route, options.cleanPathname, []));
515
529
  const response = await handleGeneratedSitemap(route, options.cleanPathname, functions);
516
530
  return response ? captureRenderedMetadataRoute(response, route, options.cleanPathname) : null;
517
531
  };
518
- const cached = await readMatchedPrerenderedMetadataRouteResponse(options, route, render);
532
+ const cached = await readMatchedPrerenderedMetadataRouteResponse(cacheOptions, route, render);
519
533
  if (cached.response) return cached.response;
520
- const response = await renderMetadataRouteMiss(options, route, render, cached.cachedEntry);
534
+ const response = await renderMetadataRouteMiss(cacheOptions, route, render, cached.cachedEntry);
521
535
  if (response) return response;
522
536
  }
523
537
  continue;
@@ -525,8 +539,12 @@ async function handleMetadataRouteRequest(options) {
525
539
  }
526
540
  const match = matchMetadataRoute(route, options.cleanPathname, functions, getUrlParts);
527
541
  if (!match) continue;
542
+ if (!matchMetadataRoute(route, routePathname, functions, getRouteUrlParts)) {
543
+ skipDecodedOwner();
544
+ continue;
545
+ }
528
546
  beginMetadataRouteCacheability(route);
529
- const rawParams = route.patternParts ? metadataRouteRawParams(route, match, options.routePathname ?? options.cleanPathname) : Object.create(null);
547
+ const rawParams = route.patternParts ? metadataRouteRawParams(route, match, routePathname) : Object.create(null);
530
548
  if (route.patternParts) {
531
549
  const runtimeParams = { ...rawParams };
532
550
  decodeMatchedParams(runtimeParams);
@@ -536,7 +554,7 @@ async function handleMetadataRouteRequest(options) {
536
554
  setCurrentFetchSoftTags(buildMetadataRouteTags(route, options.cleanPathname, []));
537
555
  return captureRenderedMetadataRoute(route.isDynamic ? await callDynamicMetadataRoute(route, match, options.makeThenableParams, functions) : serveStaticMetadataRoute(route), route, options.cleanPathname);
538
556
  };
539
- const cached = await readMatchedPrerenderedMetadataRouteResponse(options, route, render);
557
+ const cached = await readMatchedPrerenderedMetadataRouteResponse(cacheOptions, route, render);
540
558
  if (cached.response) return cached.response;
541
559
  if (route.isDynamic && isImageMetadataRoute(route) && functions.dynamicParams === false) {
542
560
  const validationParams = { ...rawParams };
@@ -552,7 +570,7 @@ async function handleMetadataRouteRequest(options) {
552
570
  });
553
571
  if (dynamicParamsResponse) return dynamicParamsResponse;
554
572
  }
555
- return renderMetadataRouteMiss(options, route, render, cached.cachedEntry);
573
+ return renderMetadataRouteMiss(cacheOptions, route, render, cached.cachedEntry);
556
574
  }
557
575
  return null;
558
576
  }
@@ -53,17 +53,26 @@ function toH3Pathname(pathname) {
53
53
  }
54
54
  }
55
55
  /**
56
+ * The key Nitro's static handler looks a pathname up under, following
57
+ * `runtime/internal/static.mjs`: without one trailing slash and decoded
58
+ * except for `%2F`.
59
+ */
60
+ function toAssetId(pathname) {
61
+ const id = pathname.length > 1 && pathname.endsWith("/") ? pathname.slice(0, -1) : pathname;
62
+ try {
63
+ return decodeURIComponent(id.replace(/%2f/gi, "%252F"));
64
+ } catch {
65
+ return id;
66
+ }
67
+ }
68
+ /**
56
69
  * Whether Nitro's static handler would answer a request for a pathname with
57
- * a public file, following its lookup in `runtime/internal/static.mjs`. It
58
- * looks h3's pathname up without one trailing slash and decoded except for
59
- * `%2F`, and then as a directory with an `index.html`, each first with the
60
- * extension of a compressed copy for every encoding the request accepts.
70
+ * a public file. It looks h3's pathname up under its key, and then as a
71
+ * directory with an `index.html`, each first with the extension of a
72
+ * compressed copy for every encoding the request accepts.
61
73
  */
62
74
  function isPublicFilePathname(pathname, extensions, publicAssets) {
63
- let id = pathname.length > 1 && pathname.endsWith("/") ? pathname.slice(0, -1) : pathname;
64
- try {
65
- id = decodeURIComponent(id.replace(/%2f/gi, "%252F"));
66
- } catch {}
75
+ const id = toAssetId(pathname);
67
76
  const index = id.endsWith("/") ? `${id}index.html` : `${id}/index.html`;
68
77
  return extensions.some((extension) => publicAssets.has(id + extension) || publicAssets.has(index + extension));
69
78
  }
@@ -86,6 +95,9 @@ function createNitroPublicFilesPlugin(options) {
86
95
  const token = crypto.randomUUID();
87
96
  Reflect.set(globalThis, Symbol.for(NITRO_ASSETS_KEY), {
88
97
  async fetch(request) {
98
+ const { pathname } = new URL(request.url);
99
+ const h3Pathname = toH3Pathname(pathname);
100
+ if (h3Pathname === void 0 || toAssetId(h3Pathname) !== toAssetId(pathname)) return new Response(null, { status: 404 });
89
101
  const headers = new Headers(request.headers);
90
102
  headers.set(ASSET_REQUEST_HEADER, token);
91
103
  return appFetch(new Request(request, { headers }));
@@ -59,8 +59,12 @@ export declare function encodeUrlParserIgnoredCharacters(pathname: string): stri
59
59
  * `/_next/data/<buildId>/en.json` → `/en`
60
60
  * `/_next/data/<wrong-id>/about.json` → null
61
61
  * `/_next/data/<buildId>/about` → null (missing .json suffix)
62
+ * `/_next/data/<buildId>/blog/...json` → null (strips to a dot segment)
63
+ *
64
+ * `decoded` marks a percent-decoded copy of a pathname whose raw form already
65
+ * parsed: a `%2e` left in it was `%252e` on the wire, an ordinary character.
62
66
  */
63
- export declare function parseNextDataPathname(pathname: string, buildId: string): NextDataMatch | null;
67
+ export declare function parseNextDataPathname(pathname: string, buildId: string, decoded?: boolean): NextDataMatch | null;
64
68
  /**
65
69
  * The page pathname a `_next/data` URL addresses, whatever its buildId. Next.js
66
70
  * compiles every middleware matcher with an optional `/_next/data/<buildId>`