@rangojs/router 0.0.0-experimental.002d056c
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.
- package/AGENTS.md +9 -0
- package/README.md +899 -0
- package/dist/bin/rango.js +1606 -0
- package/dist/vite/index.js +5153 -0
- package/package.json +177 -0
- package/skills/breadcrumbs/SKILL.md +250 -0
- package/skills/cache-guide/SKILL.md +262 -0
- package/skills/caching/SKILL.md +253 -0
- package/skills/composability/SKILL.md +172 -0
- package/skills/debug-manifest/SKILL.md +112 -0
- package/skills/document-cache/SKILL.md +182 -0
- package/skills/fonts/SKILL.md +167 -0
- package/skills/hooks/SKILL.md +704 -0
- package/skills/host-router/SKILL.md +218 -0
- package/skills/intercept/SKILL.md +313 -0
- package/skills/layout/SKILL.md +310 -0
- package/skills/links/SKILL.md +239 -0
- package/skills/loader/SKILL.md +596 -0
- package/skills/middleware/SKILL.md +339 -0
- package/skills/mime-routes/SKILL.md +128 -0
- package/skills/parallel/SKILL.md +305 -0
- package/skills/prerender/SKILL.md +643 -0
- package/skills/rango/SKILL.md +118 -0
- package/skills/response-routes/SKILL.md +411 -0
- package/skills/route/SKILL.md +385 -0
- package/skills/router-setup/SKILL.md +439 -0
- package/skills/tailwind/SKILL.md +129 -0
- package/skills/theme/SKILL.md +79 -0
- package/skills/typesafety/SKILL.md +623 -0
- package/skills/use-cache/SKILL.md +324 -0
- package/src/__internal.ts +273 -0
- package/src/bin/rango.ts +321 -0
- package/src/browser/action-coordinator.ts +97 -0
- package/src/browser/action-response-classifier.ts +99 -0
- package/src/browser/event-controller.ts +899 -0
- package/src/browser/history-state.ts +80 -0
- package/src/browser/index.ts +18 -0
- package/src/browser/intercept-utils.ts +52 -0
- package/src/browser/link-interceptor.ts +141 -0
- package/src/browser/logging.ts +55 -0
- package/src/browser/merge-segment-loaders.ts +134 -0
- package/src/browser/navigation-bridge.ts +638 -0
- package/src/browser/navigation-client.ts +261 -0
- package/src/browser/navigation-store.ts +806 -0
- package/src/browser/navigation-transaction.ts +297 -0
- package/src/browser/network-error-handler.ts +61 -0
- package/src/browser/partial-update.ts +582 -0
- package/src/browser/prefetch/cache.ts +206 -0
- package/src/browser/prefetch/fetch.ts +145 -0
- package/src/browser/prefetch/observer.ts +65 -0
- package/src/browser/prefetch/policy.ts +48 -0
- package/src/browser/prefetch/queue.ts +128 -0
- package/src/browser/rango-state.ts +112 -0
- package/src/browser/react/Link.tsx +368 -0
- package/src/browser/react/NavigationProvider.tsx +413 -0
- package/src/browser/react/ScrollRestoration.tsx +94 -0
- package/src/browser/react/context.ts +59 -0
- package/src/browser/react/filter-segment-order.ts +11 -0
- package/src/browser/react/index.ts +52 -0
- package/src/browser/react/location-state-shared.ts +162 -0
- package/src/browser/react/location-state.ts +107 -0
- package/src/browser/react/mount-context.ts +37 -0
- package/src/browser/react/nonce-context.ts +23 -0
- package/src/browser/react/shallow-equal.ts +27 -0
- package/src/browser/react/use-action.ts +218 -0
- package/src/browser/react/use-client-cache.ts +58 -0
- package/src/browser/react/use-handle.ts +162 -0
- package/src/browser/react/use-href.tsx +40 -0
- package/src/browser/react/use-link-status.ts +135 -0
- package/src/browser/react/use-mount.ts +31 -0
- package/src/browser/react/use-navigation.ts +99 -0
- package/src/browser/react/use-params.ts +65 -0
- package/src/browser/react/use-pathname.ts +47 -0
- package/src/browser/react/use-router.ts +63 -0
- package/src/browser/react/use-search-params.ts +56 -0
- package/src/browser/react/use-segments.ts +171 -0
- package/src/browser/response-adapter.ts +73 -0
- package/src/browser/rsc-router.tsx +464 -0
- package/src/browser/scroll-restoration.ts +397 -0
- package/src/browser/segment-reconciler.ts +216 -0
- package/src/browser/segment-structure-assert.ts +83 -0
- package/src/browser/server-action-bridge.ts +667 -0
- package/src/browser/shallow.ts +40 -0
- package/src/browser/types.ts +547 -0
- package/src/browser/validate-redirect-origin.ts +29 -0
- package/src/build/generate-manifest.ts +438 -0
- package/src/build/generate-route-types.ts +36 -0
- package/src/build/index.ts +35 -0
- package/src/build/route-trie.ts +265 -0
- package/src/build/route-types/ast-helpers.ts +25 -0
- package/src/build/route-types/ast-route-extraction.ts +98 -0
- package/src/build/route-types/codegen.ts +102 -0
- package/src/build/route-types/include-resolution.ts +411 -0
- package/src/build/route-types/param-extraction.ts +48 -0
- package/src/build/route-types/per-module-writer.ts +128 -0
- package/src/build/route-types/router-processing.ts +479 -0
- package/src/build/route-types/scan-filter.ts +78 -0
- package/src/build/runtime-discovery.ts +231 -0
- package/src/cache/background-task.ts +34 -0
- package/src/cache/cache-key-utils.ts +44 -0
- package/src/cache/cache-policy.ts +125 -0
- package/src/cache/cache-runtime.ts +338 -0
- package/src/cache/cache-scope.ts +382 -0
- package/src/cache/cf/cf-cache-store.ts +982 -0
- package/src/cache/cf/index.ts +29 -0
- package/src/cache/document-cache.ts +369 -0
- package/src/cache/handle-capture.ts +81 -0
- package/src/cache/handle-snapshot.ts +41 -0
- package/src/cache/index.ts +44 -0
- package/src/cache/memory-segment-store.ts +328 -0
- package/src/cache/profile-registry.ts +73 -0
- package/src/cache/read-through-swr.ts +134 -0
- package/src/cache/segment-codec.ts +256 -0
- package/src/cache/taint.ts +98 -0
- package/src/cache/types.ts +342 -0
- package/src/client.rsc.tsx +85 -0
- package/src/client.tsx +601 -0
- package/src/component-utils.ts +76 -0
- package/src/components/DefaultDocument.tsx +27 -0
- package/src/context-var.ts +86 -0
- package/src/debug.ts +243 -0
- package/src/default-error-boundary.tsx +88 -0
- package/src/deps/browser.ts +8 -0
- package/src/deps/html-stream-client.ts +2 -0
- package/src/deps/html-stream-server.ts +2 -0
- package/src/deps/rsc.ts +10 -0
- package/src/deps/ssr.ts +2 -0
- package/src/errors.ts +365 -0
- package/src/handle.ts +135 -0
- package/src/handles/MetaTags.tsx +246 -0
- package/src/handles/breadcrumbs.ts +66 -0
- package/src/handles/index.ts +7 -0
- package/src/handles/meta.ts +264 -0
- package/src/host/cookie-handler.ts +165 -0
- package/src/host/errors.ts +97 -0
- package/src/host/index.ts +53 -0
- package/src/host/pattern-matcher.ts +214 -0
- package/src/host/router.ts +352 -0
- package/src/host/testing.ts +79 -0
- package/src/host/types.ts +146 -0
- package/src/host/utils.ts +25 -0
- package/src/href-client.ts +222 -0
- package/src/index.rsc.ts +233 -0
- package/src/index.ts +277 -0
- package/src/internal-debug.ts +11 -0
- package/src/loader.rsc.ts +89 -0
- package/src/loader.ts +64 -0
- package/src/network-error-thrower.tsx +23 -0
- package/src/outlet-context.ts +15 -0
- package/src/outlet-provider.tsx +45 -0
- package/src/prerender/param-hash.ts +37 -0
- package/src/prerender/store.ts +185 -0
- package/src/prerender.ts +463 -0
- package/src/reverse.ts +330 -0
- package/src/root-error-boundary.tsx +289 -0
- package/src/route-content-wrapper.tsx +196 -0
- package/src/route-definition/dsl-helpers.ts +934 -0
- package/src/route-definition/helper-factories.ts +200 -0
- package/src/route-definition/helpers-types.ts +430 -0
- package/src/route-definition/index.ts +52 -0
- package/src/route-definition/redirect.ts +93 -0
- package/src/route-definition.ts +1 -0
- package/src/route-map-builder.ts +281 -0
- package/src/route-name.ts +53 -0
- package/src/route-types.ts +259 -0
- package/src/router/content-negotiation.ts +116 -0
- package/src/router/debug-manifest.ts +72 -0
- package/src/router/error-handling.ts +287 -0
- package/src/router/find-match.ts +160 -0
- package/src/router/handler-context.ts +451 -0
- package/src/router/intercept-resolution.ts +397 -0
- package/src/router/lazy-includes.ts +236 -0
- package/src/router/loader-resolution.ts +420 -0
- package/src/router/logging.ts +251 -0
- package/src/router/manifest.ts +269 -0
- package/src/router/match-api.ts +620 -0
- package/src/router/match-context.ts +266 -0
- package/src/router/match-handlers.ts +440 -0
- package/src/router/match-middleware/background-revalidation.ts +223 -0
- package/src/router/match-middleware/cache-lookup.ts +634 -0
- package/src/router/match-middleware/cache-store.ts +295 -0
- package/src/router/match-middleware/index.ts +81 -0
- package/src/router/match-middleware/intercept-resolution.ts +306 -0
- package/src/router/match-middleware/segment-resolution.ts +193 -0
- package/src/router/match-pipelines.ts +179 -0
- package/src/router/match-result.ts +219 -0
- package/src/router/metrics.ts +282 -0
- package/src/router/middleware-cookies.ts +55 -0
- package/src/router/middleware-types.ts +222 -0
- package/src/router/middleware.ts +749 -0
- package/src/router/pattern-matching.ts +563 -0
- package/src/router/prerender-match.ts +402 -0
- package/src/router/preview-match.ts +170 -0
- package/src/router/revalidation.ts +289 -0
- package/src/router/router-context.ts +320 -0
- package/src/router/router-interfaces.ts +452 -0
- package/src/router/router-options.ts +592 -0
- package/src/router/router-registry.ts +24 -0
- package/src/router/segment-resolution/fresh.ts +570 -0
- package/src/router/segment-resolution/helpers.ts +263 -0
- package/src/router/segment-resolution/loader-cache.ts +198 -0
- package/src/router/segment-resolution/revalidation.ts +1242 -0
- package/src/router/segment-resolution/static-store.ts +67 -0
- package/src/router/segment-resolution.ts +21 -0
- package/src/router/segment-wrappers.ts +291 -0
- package/src/router/telemetry-otel.ts +299 -0
- package/src/router/telemetry.ts +300 -0
- package/src/router/timeout.ts +148 -0
- package/src/router/trie-matching.ts +239 -0
- package/src/router/types.ts +170 -0
- package/src/router.ts +1006 -0
- package/src/rsc/handler-context.ts +45 -0
- package/src/rsc/handler.ts +1089 -0
- package/src/rsc/helpers.ts +198 -0
- package/src/rsc/index.ts +36 -0
- package/src/rsc/loader-fetch.ts +209 -0
- package/src/rsc/manifest-init.ts +86 -0
- package/src/rsc/nonce.ts +32 -0
- package/src/rsc/origin-guard.ts +141 -0
- package/src/rsc/progressive-enhancement.ts +379 -0
- package/src/rsc/response-error.ts +37 -0
- package/src/rsc/response-route-handler.ts +347 -0
- package/src/rsc/rsc-rendering.ts +237 -0
- package/src/rsc/runtime-warnings.ts +42 -0
- package/src/rsc/server-action.ts +348 -0
- package/src/rsc/ssr-setup.ts +128 -0
- package/src/rsc/types.ts +263 -0
- package/src/search-params.ts +230 -0
- package/src/segment-system.tsx +454 -0
- package/src/server/context.ts +591 -0
- package/src/server/cookie-store.ts +190 -0
- package/src/server/fetchable-loader-store.ts +37 -0
- package/src/server/handle-store.ts +308 -0
- package/src/server/loader-registry.ts +133 -0
- package/src/server/request-context.ts +920 -0
- package/src/server/root-layout.tsx +10 -0
- package/src/server/tsconfig.json +14 -0
- package/src/server.ts +51 -0
- package/src/ssr/index.tsx +365 -0
- package/src/static-handler.ts +114 -0
- package/src/theme/ThemeProvider.tsx +297 -0
- package/src/theme/ThemeScript.tsx +61 -0
- package/src/theme/constants.ts +62 -0
- package/src/theme/index.ts +48 -0
- package/src/theme/theme-context.ts +44 -0
- package/src/theme/theme-script.ts +155 -0
- package/src/theme/types.ts +182 -0
- package/src/theme/use-theme.ts +44 -0
- package/src/types/boundaries.ts +158 -0
- package/src/types/cache-types.ts +198 -0
- package/src/types/error-types.ts +192 -0
- package/src/types/global-namespace.ts +100 -0
- package/src/types/handler-context.ts +687 -0
- package/src/types/index.ts +88 -0
- package/src/types/loader-types.ts +183 -0
- package/src/types/route-config.ts +170 -0
- package/src/types/route-entry.ts +109 -0
- package/src/types/segments.ts +148 -0
- package/src/types.ts +1 -0
- package/src/urls/include-helper.ts +197 -0
- package/src/urls/index.ts +53 -0
- package/src/urls/path-helper-types.ts +339 -0
- package/src/urls/path-helper.ts +329 -0
- package/src/urls/pattern-types.ts +95 -0
- package/src/urls/response-types.ts +106 -0
- package/src/urls/type-extraction.ts +372 -0
- package/src/urls/urls-function.ts +98 -0
- package/src/urls.ts +1 -0
- package/src/use-loader.tsx +354 -0
- package/src/vite/discovery/bundle-postprocess.ts +184 -0
- package/src/vite/discovery/discover-routers.ts +344 -0
- package/src/vite/discovery/prerender-collection.ts +385 -0
- package/src/vite/discovery/route-types-writer.ts +258 -0
- package/src/vite/discovery/self-gen-tracking.ts +47 -0
- package/src/vite/discovery/state.ts +108 -0
- package/src/vite/discovery/virtual-module-codegen.ts +203 -0
- package/src/vite/index.ts +16 -0
- package/src/vite/plugin-types.ts +48 -0
- package/src/vite/plugins/cjs-to-esm.ts +93 -0
- package/src/vite/plugins/client-ref-dedup.ts +115 -0
- package/src/vite/plugins/client-ref-hashing.ts +105 -0
- package/src/vite/plugins/expose-action-id.ts +363 -0
- package/src/vite/plugins/expose-id-utils.ts +287 -0
- package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
- package/src/vite/plugins/expose-ids/handler-transform.ts +179 -0
- package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
- package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
- package/src/vite/plugins/expose-ids/types.ts +45 -0
- package/src/vite/plugins/expose-internal-ids.ts +569 -0
- package/src/vite/plugins/refresh-cmd.ts +65 -0
- package/src/vite/plugins/use-cache-transform.ts +323 -0
- package/src/vite/plugins/version-injector.ts +83 -0
- package/src/vite/plugins/version-plugin.ts +266 -0
- package/src/vite/plugins/version.d.ts +12 -0
- package/src/vite/plugins/virtual-entries.ts +123 -0
- package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
- package/src/vite/rango.ts +445 -0
- package/src/vite/router-discovery.ts +777 -0
- package/src/vite/utils/ast-handler-extract.ts +517 -0
- package/src/vite/utils/banner.ts +36 -0
- package/src/vite/utils/bundle-analysis.ts +137 -0
- package/src/vite/utils/manifest-utils.ts +70 -0
- package/src/vite/utils/package-resolution.ts +121 -0
- package/src/vite/utils/prerender-utils.ts +189 -0
- package/src/vite/utils/shared-utils.ts +169 -0
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Response Route Handler
|
|
3
|
+
*
|
|
4
|
+
* Handles response routes (JSON, text, HTML, XML, markdown, image, stream)
|
|
5
|
+
* that bypass the RSC rendering pipeline entirely. Includes content-type
|
|
6
|
+
* dispatch, route middleware execution, and response caching with SWR.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { RouterError } from "../errors.js";
|
|
10
|
+
import { requireRequestContext } from "../server/request-context.js";
|
|
11
|
+
import { contextGet } from "../context-var.js";
|
|
12
|
+
import { NOCACHE_SYMBOL } from "../cache/taint.js";
|
|
13
|
+
import { traverseBack } from "../router/pattern-matching.js";
|
|
14
|
+
import { createCacheScope } from "../cache/cache-scope.js";
|
|
15
|
+
import { executeMiddleware } from "../router/middleware.js";
|
|
16
|
+
import {
|
|
17
|
+
createReverseFunction,
|
|
18
|
+
stripInternalParams,
|
|
19
|
+
} from "../router/handler-context.js";
|
|
20
|
+
import type { MiddlewareFn } from "../router/middleware.js";
|
|
21
|
+
import type { EntryData } from "../server/context.js";
|
|
22
|
+
import type { HandlerContext } from "./handler-context.js";
|
|
23
|
+
import { createResponseErrorPayload } from "./response-error.js";
|
|
24
|
+
import {
|
|
25
|
+
createResponseWithMergedHeaders,
|
|
26
|
+
finalizeResponse,
|
|
27
|
+
isCacheableStatus,
|
|
28
|
+
buildRouteMiddlewareEntries,
|
|
29
|
+
} from "./helpers.js";
|
|
30
|
+
|
|
31
|
+
export interface ResponseRouteMatch {
|
|
32
|
+
responseType: string;
|
|
33
|
+
handler: Function;
|
|
34
|
+
params?: Record<string, string>;
|
|
35
|
+
negotiated?: boolean;
|
|
36
|
+
manifestEntry?: EntryData;
|
|
37
|
+
routeMiddleware?: Array<{
|
|
38
|
+
handler: MiddlewareFn;
|
|
39
|
+
params: Record<string, string>;
|
|
40
|
+
}>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Handle a response route (non-RSC). Dispatches by content type, wraps
|
|
45
|
+
* with route middleware and response caching when configured.
|
|
46
|
+
*
|
|
47
|
+
* For partial (client-side navigation) requests, returns X-RSC-Reload
|
|
48
|
+
* so the browser triggers a hard navigation to the response route URL.
|
|
49
|
+
*/
|
|
50
|
+
export async function handleResponseRoute<TEnv>(
|
|
51
|
+
handlerCtx: HandlerContext<TEnv>,
|
|
52
|
+
preview: ResponseRouteMatch,
|
|
53
|
+
request: Request,
|
|
54
|
+
env: TEnv,
|
|
55
|
+
url: URL,
|
|
56
|
+
variables: Record<string, any>,
|
|
57
|
+
): Promise<Response> {
|
|
58
|
+
const isPartial = url.searchParams.has("_rsc_partial");
|
|
59
|
+
|
|
60
|
+
// Partial requests (client-side navigation) to response routes
|
|
61
|
+
// get X-RSC-Reload to trigger hard navigation in the browser
|
|
62
|
+
if (isPartial) {
|
|
63
|
+
return createResponseWithMergedHeaders(null, {
|
|
64
|
+
status: 200,
|
|
65
|
+
headers: {
|
|
66
|
+
"X-RSC-Reload": stripInternalParams(url).toString(),
|
|
67
|
+
"content-type": "text/x-component;charset=utf-8",
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Build lightweight context for response handler
|
|
73
|
+
const reqCtx = requireRequestContext();
|
|
74
|
+
const cleanUrl = stripInternalParams(url);
|
|
75
|
+
const responseHandlerCtx = {
|
|
76
|
+
request,
|
|
77
|
+
params: preview.params || {},
|
|
78
|
+
env,
|
|
79
|
+
searchParams: cleanUrl.searchParams,
|
|
80
|
+
url: cleanUrl,
|
|
81
|
+
pathname: url.pathname,
|
|
82
|
+
reverse: createReverseFunction(handlerCtx.getRequiredRouteMap()),
|
|
83
|
+
get: ((keyOrVar: any) => contextGet(variables, keyOrVar)) as any,
|
|
84
|
+
header: (name: string, value: string) => reqCtx.header(name, value),
|
|
85
|
+
_responseType: preview.responseType,
|
|
86
|
+
};
|
|
87
|
+
// Brand with taint symbol so "use cache" detects it as request-scoped
|
|
88
|
+
// and extracts route-identifying properties (params, pathname, _responseType)
|
|
89
|
+
(responseHandlerCtx as any)[NOCACHE_SYMBOL] = true;
|
|
90
|
+
|
|
91
|
+
// Call handler directly, wrapped by route middleware if present
|
|
92
|
+
const callHandler = async () => {
|
|
93
|
+
const errorCtx = { request, url, env };
|
|
94
|
+
|
|
95
|
+
// Re-wrap a handler-returned Response through createResponseWithMergedHeaders
|
|
96
|
+
// so that stub headers (cookies, custom headers set via ctx.header()) are included.
|
|
97
|
+
// Use Headers (not Record<string, string>) to preserve duplicate entries like Set-Cookie.
|
|
98
|
+
const rewrapResponse = (result: Response) => {
|
|
99
|
+
const headers = new Headers();
|
|
100
|
+
result.headers.forEach((value, key) => {
|
|
101
|
+
if (key.toLowerCase() === "set-cookie") {
|
|
102
|
+
headers.append(key, value);
|
|
103
|
+
} else {
|
|
104
|
+
headers.set(key, value);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
return createResponseWithMergedHeaders(result.body, {
|
|
108
|
+
status: result.status,
|
|
109
|
+
headers,
|
|
110
|
+
});
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// JSON response routes: wrap in { data } / { error } envelope
|
|
114
|
+
if (preview.responseType === "json") {
|
|
115
|
+
try {
|
|
116
|
+
const result = await (preview.handler as Function)(responseHandlerCtx);
|
|
117
|
+
if (result instanceof Response) {
|
|
118
|
+
return rewrapResponse(result);
|
|
119
|
+
}
|
|
120
|
+
return createResponseWithMergedHeaders(
|
|
121
|
+
JSON.stringify({ data: result }),
|
|
122
|
+
{
|
|
123
|
+
status: 200,
|
|
124
|
+
headers: { "content-type": "application/json;charset=utf-8" },
|
|
125
|
+
},
|
|
126
|
+
);
|
|
127
|
+
} catch (error) {
|
|
128
|
+
handlerCtx.callOnError(error, "handler", errorCtx);
|
|
129
|
+
const isDev = process.env.NODE_ENV !== "production";
|
|
130
|
+
const status = error instanceof RouterError ? error.status : 500;
|
|
131
|
+
return createResponseWithMergedHeaders(
|
|
132
|
+
JSON.stringify({
|
|
133
|
+
error: createResponseErrorPayload(error, isDev),
|
|
134
|
+
}),
|
|
135
|
+
{
|
|
136
|
+
status,
|
|
137
|
+
headers: { "content-type": "application/json;charset=utf-8" },
|
|
138
|
+
},
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Non-JSON response routes: catch errors and return plain Response
|
|
144
|
+
try {
|
|
145
|
+
const result = await (preview.handler as Function)(responseHandlerCtx);
|
|
146
|
+
|
|
147
|
+
if (result instanceof Response) {
|
|
148
|
+
return rewrapResponse(result);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Auto-wrap based on response type tag
|
|
152
|
+
switch (preview.responseType) {
|
|
153
|
+
case "text":
|
|
154
|
+
return createResponseWithMergedHeaders(String(result), {
|
|
155
|
+
status: 200,
|
|
156
|
+
headers: { "content-type": "text/plain;charset=utf-8" },
|
|
157
|
+
});
|
|
158
|
+
case "html":
|
|
159
|
+
return createResponseWithMergedHeaders(String(result), {
|
|
160
|
+
status: 200,
|
|
161
|
+
headers: { "content-type": "text/html;charset=utf-8" },
|
|
162
|
+
});
|
|
163
|
+
case "xml":
|
|
164
|
+
return createResponseWithMergedHeaders(String(result), {
|
|
165
|
+
status: 200,
|
|
166
|
+
headers: { "content-type": "application/xml;charset=utf-8" },
|
|
167
|
+
});
|
|
168
|
+
case "md":
|
|
169
|
+
return createResponseWithMergedHeaders(String(result), {
|
|
170
|
+
status: 200,
|
|
171
|
+
headers: { "content-type": "text/markdown;charset=utf-8" },
|
|
172
|
+
});
|
|
173
|
+
default:
|
|
174
|
+
// image, stream, any -- must return Response
|
|
175
|
+
throw new Error(
|
|
176
|
+
`Response route handler for "${preview.responseType}" must return a Response object, got ${typeof result}`,
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
} catch (error) {
|
|
180
|
+
handlerCtx.callOnError(error, "handler", errorCtx);
|
|
181
|
+
const isDev = process.env.NODE_ENV !== "production";
|
|
182
|
+
const status = error instanceof RouterError ? error.status : 500;
|
|
183
|
+
const message =
|
|
184
|
+
error instanceof RouterError
|
|
185
|
+
? error.message
|
|
186
|
+
: isDev && error instanceof Error
|
|
187
|
+
? error.message
|
|
188
|
+
: "Internal Server Error";
|
|
189
|
+
return createResponseWithMergedHeaders(message, {
|
|
190
|
+
status,
|
|
191
|
+
headers: { "content-type": "text/plain;charset=utf-8" },
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
// Wrap callHandler to append Vary: Accept on content-negotiated responses
|
|
197
|
+
const callHandlerWithVary = async () => {
|
|
198
|
+
const response = await callHandler();
|
|
199
|
+
if (preview.negotiated) {
|
|
200
|
+
response.headers.append("Vary", "Accept");
|
|
201
|
+
}
|
|
202
|
+
return response;
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
// Wrap with route middleware if present
|
|
206
|
+
const executeHandler = async () => {
|
|
207
|
+
if (preview.routeMiddleware && preview.routeMiddleware.length > 0) {
|
|
208
|
+
return executeMiddleware(
|
|
209
|
+
buildRouteMiddlewareEntries<TEnv>(preview.routeMiddleware),
|
|
210
|
+
request,
|
|
211
|
+
env,
|
|
212
|
+
variables,
|
|
213
|
+
callHandlerWithVary,
|
|
214
|
+
createReverseFunction(handlerCtx.getRequiredRouteMap()),
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
return callHandlerWithVary();
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
// Resolve cache config from entry tree (same pattern as match-api.ts)
|
|
221
|
+
if (preview.manifestEntry) {
|
|
222
|
+
let cacheScope: ReturnType<typeof createCacheScope> = null;
|
|
223
|
+
for (const entry of traverseBack(preview.manifestEntry)) {
|
|
224
|
+
if (entry.cache) {
|
|
225
|
+
cacheScope = createCacheScope(entry.cache, cacheScope);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (cacheScope?.enabled) {
|
|
230
|
+
// Evaluate condition — skip response cache when condition returns false
|
|
231
|
+
let conditionPassed = true;
|
|
232
|
+
if (cacheScope.config !== false && cacheScope.config.condition) {
|
|
233
|
+
try {
|
|
234
|
+
conditionPassed = !!cacheScope.config.condition(reqCtx);
|
|
235
|
+
} catch {
|
|
236
|
+
conditionPassed = false;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const store = cacheScope.getStore() ?? reqCtx._cacheStore;
|
|
241
|
+
if (conditionPassed && store?.getResponse && store?.putResponse) {
|
|
242
|
+
// Build cache key with response:{type}: prefix to avoid collision
|
|
243
|
+
// with segment keys and differentiate between response types.
|
|
244
|
+
// Include host and url.search so query-driven and multi-host
|
|
245
|
+
// responses cache separately.
|
|
246
|
+
let cacheKey = `response:${preview.responseType}:${url.host}${url.pathname}${url.search}`;
|
|
247
|
+
|
|
248
|
+
// Priority 1: Route-level key function (full override)
|
|
249
|
+
if (cacheScope.config !== false && cacheScope.config.key) {
|
|
250
|
+
try {
|
|
251
|
+
const customKey = await cacheScope.config.key(reqCtx);
|
|
252
|
+
cacheKey = `response:${customKey}`;
|
|
253
|
+
} catch {
|
|
254
|
+
// Fall back to default key on route-level key failure
|
|
255
|
+
}
|
|
256
|
+
} else if (store.keyGenerator) {
|
|
257
|
+
// Priority 2: Store-level keyGenerator (modifies default key)
|
|
258
|
+
try {
|
|
259
|
+
cacheKey = await store.keyGenerator(reqCtx, cacheKey);
|
|
260
|
+
} catch {
|
|
261
|
+
// Fall back to default key on keyGenerator failure
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Save pre-handler callbacks (registered by app-level middleware
|
|
266
|
+
// before we reach the cache block) and clear the live array.
|
|
267
|
+
// createResponseWithMergedHeaders (inside the handler) eagerly
|
|
268
|
+
// executes any callbacks present in _onResponseCallbacks, so
|
|
269
|
+
// handler-registered callbacks are baked into the handler's
|
|
270
|
+
// response and the cached artifact. Pre-handler callbacks are
|
|
271
|
+
// NOT in the live array during execution, so they are applied
|
|
272
|
+
// once per serve on every path (hit + miss) below.
|
|
273
|
+
const savedCallbacks = reqCtx._onResponseCallbacks;
|
|
274
|
+
reqCtx._onResponseCallbacks = [];
|
|
275
|
+
|
|
276
|
+
const applyPreHandlerCallbacks = (response: Response): Response => {
|
|
277
|
+
let result = response;
|
|
278
|
+
for (const callback of savedCallbacks) {
|
|
279
|
+
result = callback(result) ?? result;
|
|
280
|
+
}
|
|
281
|
+
return result;
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
try {
|
|
285
|
+
const cached = await store.getResponse(cacheKey);
|
|
286
|
+
|
|
287
|
+
if (cached && isCacheableStatus(cached.response.status)) {
|
|
288
|
+
if (!cached.shouldRevalidate) {
|
|
289
|
+
// Fresh hit
|
|
290
|
+
return applyPreHandlerCallbacks(cached.response);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Stale hit (SWR) - return cached, revalidate in background
|
|
294
|
+
reqCtx.waitUntil(async () => {
|
|
295
|
+
try {
|
|
296
|
+
// finalizeResponse drains any onResponse callbacks registered
|
|
297
|
+
// during middleware execution (e.g. middleware short-circuit)
|
|
298
|
+
// that createResponseWithMergedHeaders didn't reach.
|
|
299
|
+
const fresh = finalizeResponse(await executeHandler());
|
|
300
|
+
if (isCacheableStatus(fresh.status)) {
|
|
301
|
+
await store.putResponse!(
|
|
302
|
+
cacheKey,
|
|
303
|
+
fresh.clone(),
|
|
304
|
+
cacheScope!.ttl,
|
|
305
|
+
cacheScope!.swr,
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
} catch (error) {
|
|
309
|
+
console.error(`[ResponseCache] Revalidation failed:`, error);
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
return applyPreHandlerCallbacks(cached.response);
|
|
314
|
+
}
|
|
315
|
+
} catch (error) {
|
|
316
|
+
console.error(`[ResponseCache] Cache lookup failed:`, error);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Cache miss - execute handler and cache the result.
|
|
320
|
+
// createResponseWithMergedHeaders inside the handler drains callbacks
|
|
321
|
+
// registered during handler execution. finalizeResponse catches any
|
|
322
|
+
// remaining callbacks (e.g. from middleware short-circuit where the
|
|
323
|
+
// handler never ran) so the cached artifact includes all transforms.
|
|
324
|
+
const response = finalizeResponse(await executeHandler());
|
|
325
|
+
|
|
326
|
+
if (isCacheableStatus(response.status)) {
|
|
327
|
+
reqCtx.waitUntil(async () => {
|
|
328
|
+
try {
|
|
329
|
+
await store.putResponse!(
|
|
330
|
+
cacheKey,
|
|
331
|
+
response.clone(),
|
|
332
|
+
cacheScope!.ttl,
|
|
333
|
+
cacheScope!.swr,
|
|
334
|
+
);
|
|
335
|
+
} catch (error) {
|
|
336
|
+
console.error(`[ResponseCache] Cache write failed:`, error);
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return applyPreHandlerCallbacks(response);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return executeHandler().then(finalizeResponse);
|
|
347
|
+
}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RSC Rendering Handler (Navigation)
|
|
3
|
+
*
|
|
4
|
+
* Handles RSC rendering for both partial (client-side navigation) and full
|
|
5
|
+
* (initial page load) requests. Includes prerender collection for build-time
|
|
6
|
+
* static generation.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
requireRequestContext,
|
|
11
|
+
setRequestContextParams,
|
|
12
|
+
getLocationState,
|
|
13
|
+
} from "../server/request-context.js";
|
|
14
|
+
import { resolveLocationStateEntries } from "../browser/react/location-state-shared.js";
|
|
15
|
+
import { appendMetric } from "../router/metrics.js";
|
|
16
|
+
import { getSSRSetup } from "./ssr-setup.js";
|
|
17
|
+
import type { RscPayload } from "./types.js";
|
|
18
|
+
import {
|
|
19
|
+
createResponseWithMergedHeaders,
|
|
20
|
+
createSimpleRedirectResponse,
|
|
21
|
+
} from "./helpers.js";
|
|
22
|
+
import type { HandlerContext } from "./handler-context.js";
|
|
23
|
+
|
|
24
|
+
export async function handleRscRendering<TEnv>(
|
|
25
|
+
ctx: HandlerContext<TEnv>,
|
|
26
|
+
request: Request,
|
|
27
|
+
env: TEnv,
|
|
28
|
+
url: URL,
|
|
29
|
+
isPartial: boolean,
|
|
30
|
+
handleStore: ReturnType<typeof requireRequestContext>["_handleStore"],
|
|
31
|
+
nonce: string | undefined,
|
|
32
|
+
): Promise<Response> {
|
|
33
|
+
const reqCtx = requireRequestContext();
|
|
34
|
+
|
|
35
|
+
let payload: RscPayload;
|
|
36
|
+
let hasInterceptSlots = false;
|
|
37
|
+
|
|
38
|
+
if (isPartial) {
|
|
39
|
+
// Partial render (navigation)
|
|
40
|
+
const result = await ctx.router.matchPartial(request, { env });
|
|
41
|
+
|
|
42
|
+
if (!result) {
|
|
43
|
+
// Fall back to full render
|
|
44
|
+
const match = await ctx.router.match(request, { env });
|
|
45
|
+
setRequestContextParams(match.params, match.routeName);
|
|
46
|
+
|
|
47
|
+
if (match.redirect) {
|
|
48
|
+
// Partial request: use X-RSC-Redirect header so the client can
|
|
49
|
+
// perform SPA navigation. A raw 308 would be auto-followed by
|
|
50
|
+
// fetch, hitting the target without _rsc_partial.
|
|
51
|
+
return createSimpleRedirectResponse(match.redirect);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
payload = {
|
|
55
|
+
metadata: {
|
|
56
|
+
pathname: url.pathname,
|
|
57
|
+
segments: match.segments,
|
|
58
|
+
matched: match.matched,
|
|
59
|
+
diff: match.diff,
|
|
60
|
+
params: match.params,
|
|
61
|
+
isPartial: false,
|
|
62
|
+
rootLayout: ctx.router.rootLayout,
|
|
63
|
+
handles: handleStore.stream(),
|
|
64
|
+
version: ctx.version,
|
|
65
|
+
prefetchCacheTTL: ctx.router.prefetchCacheTTL,
|
|
66
|
+
themeConfig: ctx.router.themeConfig,
|
|
67
|
+
initialTheme: reqCtx.theme,
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
} else {
|
|
71
|
+
setRequestContextParams(result.params, result.routeName);
|
|
72
|
+
|
|
73
|
+
hasInterceptSlots = !!result.slots;
|
|
74
|
+
|
|
75
|
+
payload = {
|
|
76
|
+
metadata: {
|
|
77
|
+
pathname: url.pathname,
|
|
78
|
+
segments: result.segments,
|
|
79
|
+
matched: result.matched,
|
|
80
|
+
diff: result.diff,
|
|
81
|
+
params: result.params,
|
|
82
|
+
isPartial: true,
|
|
83
|
+
slots: result.slots,
|
|
84
|
+
handles: handleStore.stream(),
|
|
85
|
+
version: ctx.version,
|
|
86
|
+
prefetchCacheTTL: ctx.router.prefetchCacheTTL,
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
} else {
|
|
91
|
+
// Full render (initial page load)
|
|
92
|
+
const match = await ctx.router.match(request, { env });
|
|
93
|
+
setRequestContextParams(match.params, match.routeName);
|
|
94
|
+
|
|
95
|
+
if (match.redirect) {
|
|
96
|
+
return createResponseWithMergedHeaders(null, {
|
|
97
|
+
status: 308,
|
|
98
|
+
headers: { Location: match.redirect },
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Caching is now handled in router.match() via cache provider in request context
|
|
103
|
+
// match.segments already contains cached or fresh segments as appropriate
|
|
104
|
+
|
|
105
|
+
if (url.searchParams.has("__prerender_collect")) {
|
|
106
|
+
// Build-time prerender collection: serialize segments and handle data
|
|
107
|
+
// to JSON for storage as build artifacts. At runtime the worker
|
|
108
|
+
// deserializes these and feeds them through the normal segment pipeline.
|
|
109
|
+
const nonLoaderSegments = match.segments.filter(
|
|
110
|
+
(s) => s.type !== "loader",
|
|
111
|
+
);
|
|
112
|
+
handleStore.seal();
|
|
113
|
+
await handleStore.settled;
|
|
114
|
+
const { serializeSegments } = await import("../cache/segment-codec.js");
|
|
115
|
+
const serializedSegments = await serializeSegments(nonLoaderSegments);
|
|
116
|
+
const handles: Record<string, Record<string, unknown[]>> = {};
|
|
117
|
+
for (const seg of nonLoaderSegments) {
|
|
118
|
+
const segHandles = handleStore.getDataForSegment(seg.id);
|
|
119
|
+
if (Object.keys(segHandles).length > 0) {
|
|
120
|
+
handles[seg.id] = segHandles;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return new Response(
|
|
124
|
+
JSON.stringify({
|
|
125
|
+
segments: serializedSegments,
|
|
126
|
+
handles,
|
|
127
|
+
routeName: match.routeName,
|
|
128
|
+
params: match.params,
|
|
129
|
+
}),
|
|
130
|
+
{ headers: { "Content-Type": "application/json" } },
|
|
131
|
+
);
|
|
132
|
+
} else {
|
|
133
|
+
payload = {
|
|
134
|
+
// Initial SSR can reconstruct the tree from segments + rootLayout,
|
|
135
|
+
// so we omit root to avoid sending the same structure twice.
|
|
136
|
+
|
|
137
|
+
metadata: {
|
|
138
|
+
pathname: url.pathname,
|
|
139
|
+
segments: match.segments,
|
|
140
|
+
matched: match.matched,
|
|
141
|
+
diff: match.diff,
|
|
142
|
+
params: match.params,
|
|
143
|
+
isPartial: false,
|
|
144
|
+
rootLayout: ctx.router.rootLayout,
|
|
145
|
+
handles: handleStore.stream(),
|
|
146
|
+
version: ctx.version,
|
|
147
|
+
prefetchCacheTTL: ctx.router.prefetchCacheTTL,
|
|
148
|
+
themeConfig: ctx.router.themeConfig,
|
|
149
|
+
initialTheme: reqCtx.theme,
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// For partial requests, include any server-set location state in the payload.
|
|
156
|
+
// SSR (full page) requests ignore location state since there's no history.state
|
|
157
|
+
// to write to on a fresh page load.
|
|
158
|
+
if (isPartial && payload.metadata) {
|
|
159
|
+
const locationState = getLocationState();
|
|
160
|
+
if (locationState) {
|
|
161
|
+
payload.metadata.locationState =
|
|
162
|
+
resolveLocationStateEntries(locationState);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const metricsStore = reqCtx._metricsStore;
|
|
167
|
+
const renderStart = performance.now();
|
|
168
|
+
|
|
169
|
+
// Serialize to RSC stream
|
|
170
|
+
const rscSerializeStart = performance.now();
|
|
171
|
+
const rscStream = ctx.renderToReadableStream<RscPayload>(payload);
|
|
172
|
+
const rscSerializeDur = performance.now() - rscSerializeStart;
|
|
173
|
+
// This measures synchronous stream creation, not end-to-end stream consumption.
|
|
174
|
+
appendMetric(
|
|
175
|
+
metricsStore,
|
|
176
|
+
"rsc-serialize",
|
|
177
|
+
rscSerializeStart,
|
|
178
|
+
rscSerializeDur,
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
// Determine if this is an RSC request or HTML request.
|
|
182
|
+
// Partial requests (_rsc_partial) are always RSC -- they come from client-side
|
|
183
|
+
// navigation or prefetch fetch(). We cannot rely on Accept alone since some
|
|
184
|
+
// browsers may send Accept: text/html for non-HTML requests.
|
|
185
|
+
const isRscRequest =
|
|
186
|
+
isPartial ||
|
|
187
|
+
(!request.headers.get("accept")?.includes("text/html") &&
|
|
188
|
+
!url.searchParams.has("__html")) ||
|
|
189
|
+
url.searchParams.has("__rsc");
|
|
190
|
+
|
|
191
|
+
if (isRscRequest) {
|
|
192
|
+
const renderDur = performance.now() - renderStart;
|
|
193
|
+
appendMetric(metricsStore, "render:total", renderStart, renderDur);
|
|
194
|
+
const rscHeaders: Record<string, string> = {
|
|
195
|
+
"content-type": "text/x-component;charset=utf-8",
|
|
196
|
+
vary: "accept, X-Rango-State, X-RSC-Router-Client-Path",
|
|
197
|
+
};
|
|
198
|
+
// Enable browser HTTP caching for prefetch responses only.
|
|
199
|
+
// Requires X-Rango-Prefetch header (sent by Link prefetch fetch),
|
|
200
|
+
// non-intercept context (intercept responses depend on source page),
|
|
201
|
+
// and a configured cache-control value (false disables caching).
|
|
202
|
+
const isPrefetch = request.headers.has("X-Rango-Prefetch");
|
|
203
|
+
if (isPrefetch && isPartial && !hasInterceptSlots) {
|
|
204
|
+
const cc = ctx.router.prefetchCacheControl;
|
|
205
|
+
if (cc) {
|
|
206
|
+
rscHeaders["cache-control"] = cc;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return createResponseWithMergedHeaders(rscStream, {
|
|
210
|
+
headers: rscHeaders,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Delegate to SSR for HTML response (reuse early setup if available)
|
|
215
|
+
const [ssrModule, streamMode] = await getSSRSetup(
|
|
216
|
+
ctx,
|
|
217
|
+
request,
|
|
218
|
+
env,
|
|
219
|
+
url,
|
|
220
|
+
metricsStore,
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
const ssrRenderStart = performance.now();
|
|
224
|
+
const htmlStream = await ssrModule.renderHTML(rscStream, {
|
|
225
|
+
nonce,
|
|
226
|
+
streamMode,
|
|
227
|
+
});
|
|
228
|
+
const ssrRenderDur = performance.now() - ssrRenderStart;
|
|
229
|
+
appendMetric(metricsStore, "ssr-render-html", ssrRenderStart, ssrRenderDur);
|
|
230
|
+
|
|
231
|
+
const renderDur = performance.now() - renderStart;
|
|
232
|
+
appendMetric(metricsStore, "render:total", renderStart, renderDur);
|
|
233
|
+
|
|
234
|
+
return createResponseWithMergedHeaders(htmlStream, {
|
|
235
|
+
headers: { "content-type": "text/html;charset=utf-8" },
|
|
236
|
+
});
|
|
237
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime guardrail warnings (dev-only).
|
|
3
|
+
*
|
|
4
|
+
* W3: PE action redirect / Response handling.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
createResponseWithMergedHeaders,
|
|
9
|
+
carryOverRedirectHeaders,
|
|
10
|
+
} from "./helpers.js";
|
|
11
|
+
|
|
12
|
+
// W3 -----------------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Extract a redirect Response from a thrown or returned value.
|
|
16
|
+
* Returns a redirect Response to send to the client, or null if the value
|
|
17
|
+
* is not a redirect Response.
|
|
18
|
+
*/
|
|
19
|
+
export function extractRedirectResponse(value: unknown): Response | null {
|
|
20
|
+
if (!(value instanceof Response)) return null;
|
|
21
|
+
const location = value.headers.get("Location");
|
|
22
|
+
if (value.status >= 300 && value.status < 400 && location) {
|
|
23
|
+
const redirect = createResponseWithMergedHeaders(null, {
|
|
24
|
+
status: value.status,
|
|
25
|
+
headers: { Location: location },
|
|
26
|
+
});
|
|
27
|
+
carryOverRedirectHeaders(value, redirect);
|
|
28
|
+
return redirect;
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Warn when a non-redirect Response is returned from an action during PE.
|
|
35
|
+
*/
|
|
36
|
+
export function warnNonRedirectPeResponse(): void {
|
|
37
|
+
console.warn(
|
|
38
|
+
`[rango] Server action returned a non-redirect Response during ` +
|
|
39
|
+
`progressive enhancement (no-JS) request. The Response will be ` +
|
|
40
|
+
`ignored — the page will re-render at the current URL instead.`,
|
|
41
|
+
);
|
|
42
|
+
}
|