@rangojs/router 0.0.0-experimental.4 → 0.0.0-experimental.6
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/dist/vite/index.js +1 -1
- package/package.json +1 -1
- package/skills/router-setup/SKILL.md +31 -0
- package/src/browser/react/NavigationProvider.tsx +89 -0
- package/src/browser/react/mount-context.ts +19 -1
- package/src/browser/rsc-router.tsx +5 -5
- package/src/handles/MetaTags.tsx +0 -4
- package/src/rsc/handler.ts +0 -2
- package/src/segment-system.tsx +6 -3
- package/src/warmup/connection-warmup.tsx +0 -94
- package/src/warmup/warmup-context.ts +0 -35
package/dist/vite/index.js
CHANGED
|
@@ -714,7 +714,7 @@ import { resolve } from "node:path";
|
|
|
714
714
|
// package.json
|
|
715
715
|
var package_default = {
|
|
716
716
|
name: "@rangojs/router",
|
|
717
|
-
version: "0.0.0-experimental.
|
|
717
|
+
version: "0.0.0-experimental.6",
|
|
718
718
|
type: "module",
|
|
719
719
|
description: "Django-inspired RSC router with composable URL patterns",
|
|
720
720
|
author: "Ivo Todorov",
|
package/package.json
CHANGED
|
@@ -97,6 +97,9 @@ interface RSCRouterOptions<TEnv> {
|
|
|
97
97
|
// Theme configuration
|
|
98
98
|
theme?: ThemeConfig | true;
|
|
99
99
|
|
|
100
|
+
// Connection warmup (default: true)
|
|
101
|
+
warmup?: boolean;
|
|
102
|
+
|
|
100
103
|
// CSP nonce provider (for router.fetch)
|
|
101
104
|
nonce?: (request: Request, env: TEnv) => string | true | Promise<string | true>;
|
|
102
105
|
|
|
@@ -313,3 +316,31 @@ const router = createRouter<AppEnv>({
|
|
|
313
316
|
urls: urlpatterns,
|
|
314
317
|
});
|
|
315
318
|
```
|
|
319
|
+
|
|
320
|
+
## Connection Warmup
|
|
321
|
+
|
|
322
|
+
Enabled by default. Keeps TCP+TLS connections alive so navigations after idle periods
|
|
323
|
+
don't pay handshake costs.
|
|
324
|
+
|
|
325
|
+
After 60s of no user interaction, the connection is marked cold. When the user returns
|
|
326
|
+
(tab becomes visible or first mouse/touch), a `HEAD ?_rsc_warmup` request re-establishes
|
|
327
|
+
the TLS connection before the next navigation. The server responds with 204 No Content
|
|
328
|
+
before any middleware or routing runs.
|
|
329
|
+
|
|
330
|
+
```typescript
|
|
331
|
+
// Enabled by default
|
|
332
|
+
const router = createRouter({
|
|
333
|
+
document: Document,
|
|
334
|
+
urls: urlpatterns,
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
// Disable warmup
|
|
338
|
+
const router = createRouter({
|
|
339
|
+
document: Document,
|
|
340
|
+
urls: urlpatterns,
|
|
341
|
+
warmup: false,
|
|
342
|
+
});
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
The warmup request is relative to the current page path, so it works correctly
|
|
346
|
+
with subpath deployments (reverse proxy, base path).
|
|
@@ -120,6 +120,12 @@ export interface NavigationProviderProps {
|
|
|
120
120
|
* Only used when themeConfig is provided
|
|
121
121
|
*/
|
|
122
122
|
initialTheme?: Theme;
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Whether connection warmup is enabled.
|
|
126
|
+
* When true, keeps TLS alive by sending HEAD requests after idle periods.
|
|
127
|
+
*/
|
|
128
|
+
warmupEnabled?: boolean;
|
|
123
129
|
}
|
|
124
130
|
|
|
125
131
|
/**
|
|
@@ -150,6 +156,7 @@ export function NavigationProvider({
|
|
|
150
156
|
bridge,
|
|
151
157
|
themeConfig,
|
|
152
158
|
initialTheme,
|
|
159
|
+
warmupEnabled,
|
|
153
160
|
}: NavigationProviderProps): ReactNode {
|
|
154
161
|
// Track current payload for rendering (this triggers re-renders)
|
|
155
162
|
const [payload, setPayload] = useState(initialPayload);
|
|
@@ -182,6 +189,88 @@ export function NavigationProvider({
|
|
|
182
189
|
[]
|
|
183
190
|
);
|
|
184
191
|
|
|
192
|
+
// Connection warmup: keep TLS alive after idle periods.
|
|
193
|
+
// After 60s of no user interaction, marks connection as "cold".
|
|
194
|
+
// On next interaction or visibility change, sends a HEAD request to warm TLS
|
|
195
|
+
// before the user actually clicks a link.
|
|
196
|
+
useEffect(() => {
|
|
197
|
+
if (!warmupEnabled) return;
|
|
198
|
+
|
|
199
|
+
const IDLE_TIMEOUT = 60_000;
|
|
200
|
+
const DEBOUNCE_DELAY = 150;
|
|
201
|
+
|
|
202
|
+
let idleTimer: ReturnType<typeof setTimeout> | undefined;
|
|
203
|
+
let debounceTimer: ReturnType<typeof setTimeout> | undefined;
|
|
204
|
+
let isCold = false;
|
|
205
|
+
let warmupListenersAttached = false;
|
|
206
|
+
|
|
207
|
+
function sendWarmup() {
|
|
208
|
+
isCold = false;
|
|
209
|
+
fetch("/?_rsc_warmup", { method: "HEAD" }).catch(() => {});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function triggerWarmup() {
|
|
213
|
+
if (!isCold) return;
|
|
214
|
+
clearTimeout(debounceTimer);
|
|
215
|
+
debounceTimer = setTimeout(() => {
|
|
216
|
+
sendWarmup();
|
|
217
|
+
detachWarmupListeners();
|
|
218
|
+
resetIdleTimer();
|
|
219
|
+
}, DEBOUNCE_DELAY);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function onVisibilityChange() {
|
|
223
|
+
if (document.visibilityState === "visible" && isCold) {
|
|
224
|
+
triggerWarmup();
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function attachWarmupListeners() {
|
|
229
|
+
if (warmupListenersAttached) return;
|
|
230
|
+
warmupListenersAttached = true;
|
|
231
|
+
document.addEventListener("visibilitychange", onVisibilityChange);
|
|
232
|
+
document.addEventListener("mousemove", triggerWarmup, { once: true });
|
|
233
|
+
document.addEventListener("touchstart", triggerWarmup, { once: true });
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function detachWarmupListeners() {
|
|
237
|
+
warmupListenersAttached = false;
|
|
238
|
+
document.removeEventListener("visibilitychange", onVisibilityChange);
|
|
239
|
+
document.removeEventListener("mousemove", triggerWarmup);
|
|
240
|
+
document.removeEventListener("touchstart", triggerWarmup);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function markCold() {
|
|
244
|
+
isCold = true;
|
|
245
|
+
attachWarmupListeners();
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function resetIdleTimer() {
|
|
249
|
+
clearTimeout(idleTimer);
|
|
250
|
+
isCold = false;
|
|
251
|
+
idleTimer = setTimeout(markCold, IDLE_TIMEOUT);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Activity events that reset the idle timer
|
|
255
|
+
const activityEvents = ["mousemove", "keydown", "touchstart", "scroll"] as const;
|
|
256
|
+
const activityOptions: AddEventListenerOptions = { passive: true };
|
|
257
|
+
|
|
258
|
+
for (const event of activityEvents) {
|
|
259
|
+
document.addEventListener(event, resetIdleTimer, activityOptions);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
resetIdleTimer();
|
|
263
|
+
|
|
264
|
+
return () => {
|
|
265
|
+
clearTimeout(idleTimer);
|
|
266
|
+
clearTimeout(debounceTimer);
|
|
267
|
+
detachWarmupListeners();
|
|
268
|
+
for (const event of activityEvents) {
|
|
269
|
+
document.removeEventListener(event, resetIdleTimer);
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
}, [warmupEnabled]);
|
|
273
|
+
|
|
185
274
|
// Subscribe to UI updates (for re-rendering the tree)
|
|
186
275
|
useEffect(() => {
|
|
187
276
|
const unsubscribe = store.onUpdate((update) => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { createContext, type Context } from "react";
|
|
3
|
+
import { createContext, createElement, type Context, type ReactNode } from "react";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Context for the current include() mount path.
|
|
@@ -12,3 +12,21 @@ import { createContext, type Context } from "react";
|
|
|
12
12
|
* Default value "/" means root-level (no include wrapping).
|
|
13
13
|
*/
|
|
14
14
|
export const MountContext: Context<string> = createContext<string>("/");
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Provider wrapper for MountContext.
|
|
18
|
+
*
|
|
19
|
+
* RSC server components cannot use MountContext.Provider directly because
|
|
20
|
+
* .Provider is a property on the context object, not a named export.
|
|
21
|
+
* Client reference proxies on the RSC server return undefined for property
|
|
22
|
+
* access. This wrapper is a proper "use client" export that RSC can reference.
|
|
23
|
+
*/
|
|
24
|
+
export function MountContextProvider({
|
|
25
|
+
value,
|
|
26
|
+
children,
|
|
27
|
+
}: {
|
|
28
|
+
value: string;
|
|
29
|
+
children: ReactNode;
|
|
30
|
+
}): ReactNode {
|
|
31
|
+
return createElement(MountContext, { value }, children);
|
|
32
|
+
}
|
|
@@ -13,7 +13,6 @@ import { createServerActionBridge } from "./server-action-bridge.js";
|
|
|
13
13
|
import { createNavigationBridge } from "./navigation-bridge.js";
|
|
14
14
|
import { NavigationProvider, initHandleDataSync, initSegmentsSync } from "./react/index.js";
|
|
15
15
|
import { initThemeConfigSync } from "../theme/theme-context.js";
|
|
16
|
-
import { initWarmupSync } from "../warmup/warmup-context.js";
|
|
17
16
|
import type {
|
|
18
17
|
RscPayload,
|
|
19
18
|
RscBrowserDependencies,
|
|
@@ -105,6 +104,8 @@ export interface BrowserAppContext {
|
|
|
105
104
|
themeConfig?: ResolvedThemeConfig | null;
|
|
106
105
|
/** Initial theme from server */
|
|
107
106
|
initialTheme?: Theme;
|
|
107
|
+
/** Whether connection warmup is enabled */
|
|
108
|
+
warmupEnabled?: boolean;
|
|
108
109
|
}
|
|
109
110
|
|
|
110
111
|
// Module-level state for the initialized app
|
|
@@ -158,9 +159,6 @@ export async function initBrowserApp(
|
|
|
158
159
|
// Initialize theme config for MetaTags (must match SSR state)
|
|
159
160
|
initThemeConfigSync(effectiveThemeConfig);
|
|
160
161
|
|
|
161
|
-
// Initialize warmup config for MetaTags (must match SSR state)
|
|
162
|
-
initWarmupSync(initialPayload.metadata?.warmupEnabled ?? true);
|
|
163
|
-
|
|
164
162
|
// Initialize event controller with segment order (even without handles)
|
|
165
163
|
eventController.setHandleData({}, initialPayload.metadata?.matched);
|
|
166
164
|
|
|
@@ -279,6 +277,7 @@ export async function initBrowserApp(
|
|
|
279
277
|
initialTree,
|
|
280
278
|
themeConfig: effectiveThemeConfig,
|
|
281
279
|
initialTheme: effectiveInitialTheme,
|
|
280
|
+
warmupEnabled: initialPayload.metadata?.warmupEnabled ?? true,
|
|
282
281
|
};
|
|
283
282
|
browserAppContext = context;
|
|
284
283
|
|
|
@@ -334,7 +333,7 @@ export interface RSCRouterProps {}
|
|
|
334
333
|
* ```
|
|
335
334
|
*/
|
|
336
335
|
export function RSCRouter(_props: RSCRouterProps): React.ReactElement {
|
|
337
|
-
const { store, eventController, bridge, initialPayload, initialTree, themeConfig, initialTheme } =
|
|
336
|
+
const { store, eventController, bridge, initialPayload, initialTree, themeConfig, initialTheme, warmupEnabled } =
|
|
338
337
|
getBrowserAppContext();
|
|
339
338
|
|
|
340
339
|
return (
|
|
@@ -345,6 +344,7 @@ export function RSCRouter(_props: RSCRouterProps): React.ReactElement {
|
|
|
345
344
|
bridge={bridge}
|
|
346
345
|
themeConfig={themeConfig}
|
|
347
346
|
initialTheme={initialTheme}
|
|
347
|
+
warmupEnabled={warmupEnabled}
|
|
348
348
|
/>
|
|
349
349
|
);
|
|
350
350
|
}
|
package/src/handles/MetaTags.tsx
CHANGED
|
@@ -30,8 +30,6 @@ import { Meta } from "./meta.js";
|
|
|
30
30
|
import type { MetaDescriptor, MetaDescriptorBase } from "../router/types.js";
|
|
31
31
|
import { getSSRThemeConfig } from "../theme/theme-context.js";
|
|
32
32
|
import { generateThemeScript } from "../theme/theme-script.js";
|
|
33
|
-
import { getSSRWarmupEnabled } from "../warmup/warmup-context.js";
|
|
34
|
-
import { ConnectionWarmup } from "../warmup/connection-warmup.js";
|
|
35
33
|
|
|
36
34
|
// Type guards for MetaDescriptorBase variants
|
|
37
35
|
function hasCharSet(d: MetaDescriptorBase): d is { charSet: "utf-8" } {
|
|
@@ -175,7 +173,6 @@ function AsyncMetaTag({ promise, index }: { promise: Promise<MetaDescriptorBase>
|
|
|
175
173
|
export function MetaTags(): React.ReactNode {
|
|
176
174
|
const descriptors = useHandle(Meta) as MetaDescriptor[];
|
|
177
175
|
const themeConfig = getSSRThemeConfig();
|
|
178
|
-
const warmupEnabled = getSSRWarmupEnabled();
|
|
179
176
|
|
|
180
177
|
return (
|
|
181
178
|
<>
|
|
@@ -191,7 +188,6 @@ export function MetaTags(): React.ReactNode {
|
|
|
191
188
|
}
|
|
192
189
|
return renderMetaDescriptor(descriptor, index);
|
|
193
190
|
})}
|
|
194
|
-
{warmupEnabled && <ConnectionWarmup />}
|
|
195
191
|
</>
|
|
196
192
|
);
|
|
197
193
|
}
|
package/src/rsc/handler.ts
CHANGED
|
@@ -977,7 +977,6 @@ export function createRSCHandler<
|
|
|
977
977
|
handles: handleStore.stream(),
|
|
978
978
|
version,
|
|
979
979
|
themeConfig: router.themeConfig,
|
|
980
|
-
warmupEnabled: router.warmupEnabled,
|
|
981
980
|
initialTheme: requireRequestContext().theme,
|
|
982
981
|
},
|
|
983
982
|
};
|
|
@@ -1035,7 +1034,6 @@ export function createRSCHandler<
|
|
|
1035
1034
|
handles: handleStore.stream(),
|
|
1036
1035
|
version,
|
|
1037
1036
|
themeConfig: router.themeConfig,
|
|
1038
|
-
warmupEnabled: router.warmupEnabled,
|
|
1039
1037
|
initialTheme: requireRequestContext().theme,
|
|
1040
1038
|
},
|
|
1041
1039
|
};
|
package/src/segment-system.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createElement, type ReactNode, type ComponentType } from "react";
|
|
2
2
|
import { OutletProvider } from "./client.js";
|
|
3
|
-
import {
|
|
3
|
+
import { MountContextProvider } from "./browser/react/mount-context.js";
|
|
4
4
|
import type {
|
|
5
5
|
ResolvedSegment,
|
|
6
6
|
LoaderDataResult,
|
|
@@ -282,9 +282,12 @@ export async function renderSegments(
|
|
|
282
282
|
});
|
|
283
283
|
}
|
|
284
284
|
|
|
285
|
-
// Wrap with
|
|
285
|
+
// Wrap with MountContextProvider for include() scoped components.
|
|
286
|
+
// Must use MountContextProvider (a proper "use client" export) instead of
|
|
287
|
+
// MountContext.Provider directly, because .Provider is a property on the
|
|
288
|
+
// context object and resolves to undefined through RSC client reference proxies.
|
|
286
289
|
if (node.segment.mountPath && node.segment.type === "layout") {
|
|
287
|
-
content = createElement(
|
|
290
|
+
content = createElement(MountContextProvider, {
|
|
288
291
|
value: node.segment.mountPath,
|
|
289
292
|
children: content,
|
|
290
293
|
});
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Connection warmup component.
|
|
5
|
-
*
|
|
6
|
-
* Keeps TCP+TLS connections alive so navigations after idle periods
|
|
7
|
-
* don't pay DNS+TCP+TLS handshake costs. Sends a HEAD request with
|
|
8
|
-
* ?_rsc_warmup when the connection goes cold and the user returns.
|
|
9
|
-
*
|
|
10
|
-
* Cold detection: 60s of no user interaction marks the connection as cold.
|
|
11
|
-
* Warmup triggers: on visibility change or first user interaction after cold,
|
|
12
|
-
* debounced 150ms, sends HEAD /?_rsc_warmup to re-establish TLS.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import { useEffect } from "react";
|
|
16
|
-
|
|
17
|
-
const IDLE_TIMEOUT = 60_000;
|
|
18
|
-
const DEBOUNCE_MS = 150;
|
|
19
|
-
|
|
20
|
-
export function ConnectionWarmup(): null {
|
|
21
|
-
useEffect(() => {
|
|
22
|
-
let idleTimer: ReturnType<typeof setTimeout> | undefined;
|
|
23
|
-
let debounceTimer: ReturnType<typeof setTimeout> | undefined;
|
|
24
|
-
let isCold = false;
|
|
25
|
-
let warmupListenersAttached = false;
|
|
26
|
-
|
|
27
|
-
// Reset idle timer on any activity
|
|
28
|
-
function resetIdleTimer(): void {
|
|
29
|
-
isCold = false;
|
|
30
|
-
clearTimeout(idleTimer);
|
|
31
|
-
idleTimer = setTimeout(() => {
|
|
32
|
-
isCold = true;
|
|
33
|
-
attachWarmupListeners();
|
|
34
|
-
}, IDLE_TIMEOUT);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Send the warmup HEAD request (debounced)
|
|
38
|
-
function triggerWarmup(): void {
|
|
39
|
-
if (!isCold) return;
|
|
40
|
-
clearTimeout(debounceTimer);
|
|
41
|
-
debounceTimer = setTimeout(() => {
|
|
42
|
-
fetch("/?_rsc_warmup", { method: "HEAD" }).catch(() => {});
|
|
43
|
-
isCold = false;
|
|
44
|
-
// Detach warmup listeners until next cold period
|
|
45
|
-
detachWarmupListeners();
|
|
46
|
-
resetIdleTimer();
|
|
47
|
-
}, DEBOUNCE_MS);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Visibility change handler (fires even without mouse/touch)
|
|
51
|
-
function onVisibilityChange(): void {
|
|
52
|
-
if (document.visibilityState === "visible" && isCold) {
|
|
53
|
-
triggerWarmup();
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Warmup listeners are only active while cold
|
|
58
|
-
function attachWarmupListeners(): void {
|
|
59
|
-
if (warmupListenersAttached) return;
|
|
60
|
-
warmupListenersAttached = true;
|
|
61
|
-
document.addEventListener("visibilitychange", onVisibilityChange);
|
|
62
|
-
document.addEventListener("mousemove", triggerWarmup, { once: true });
|
|
63
|
-
document.addEventListener("touchstart", triggerWarmup, { once: true });
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function detachWarmupListeners(): void {
|
|
67
|
-
if (!warmupListenersAttached) return;
|
|
68
|
-
warmupListenersAttached = false;
|
|
69
|
-
document.removeEventListener("visibilitychange", onVisibilityChange);
|
|
70
|
-
document.removeEventListener("mousemove", triggerWarmup);
|
|
71
|
-
document.removeEventListener("touchstart", triggerWarmup);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Track activity for idle detection
|
|
75
|
-
const activityEvents = ["mousemove", "keydown", "touchstart", "scroll"] as const;
|
|
76
|
-
for (const event of activityEvents) {
|
|
77
|
-
document.addEventListener(event, resetIdleTimer, { passive: true });
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Start idle timer immediately
|
|
81
|
-
resetIdleTimer();
|
|
82
|
-
|
|
83
|
-
return () => {
|
|
84
|
-
clearTimeout(idleTimer);
|
|
85
|
-
clearTimeout(debounceTimer);
|
|
86
|
-
detachWarmupListeners();
|
|
87
|
-
for (const event of activityEvents) {
|
|
88
|
-
document.removeEventListener(event, resetIdleTimer);
|
|
89
|
-
}
|
|
90
|
-
};
|
|
91
|
-
}, []);
|
|
92
|
-
|
|
93
|
-
return null;
|
|
94
|
-
}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Warmup context for connection keep-alive configuration.
|
|
5
|
-
*
|
|
6
|
-
* Module-level state populated during browser init (initWarmupSync)
|
|
7
|
-
* and read during SSR (getSSRWarmupEnabled) by MetaTags.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* SSR module-level state for warmup enabled flag.
|
|
12
|
-
* Populated by initWarmupSync before React renders.
|
|
13
|
-
* Used by MetaTags during SSR to conditionally render ConnectionWarmup.
|
|
14
|
-
*/
|
|
15
|
-
let ssrWarmupEnabled = true;
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Initialize warmup config synchronously for SSR.
|
|
19
|
-
* Called before rendering to populate state for MetaTags.
|
|
20
|
-
*
|
|
21
|
-
* @param enabled - Whether connection warmup is enabled
|
|
22
|
-
*/
|
|
23
|
-
export function initWarmupSync(enabled: boolean): void {
|
|
24
|
-
ssrWarmupEnabled = enabled;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Get warmup enabled flag for SSR/hydration.
|
|
29
|
-
* Used by MetaTags to conditionally render ConnectionWarmup.
|
|
30
|
-
*
|
|
31
|
-
* @returns Whether warmup is enabled
|
|
32
|
-
*/
|
|
33
|
-
export function getSSRWarmupEnabled(): boolean {
|
|
34
|
-
return ssrWarmupEnabled;
|
|
35
|
-
}
|