forked from CopilotKit/CopilotKit
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathruntime-config.client.ts
More file actions
81 lines (76 loc) · 3.34 KB
/
Copy pathruntime-config.client.ts
File metadata and controls
81 lines (76 loc) · 3.34 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
// Client-side runtime config reader for shell-docs. Reads from
// window.__SHOWCASE_CONFIG__ which the root layout injects via an
// inline <script> tag BEFORE React hydrates (see app/layout.tsx).
// This is the ONLY public API for these URLs/keys in client code —
// never read process.env.NEXT_PUBLIC_* directly.
import type { RuntimeConfig } from "./runtime-config";
export type { RuntimeConfig };
declare global {
interface Window {
__SHOWCASE_CONFIG__?: RuntimeConfig;
}
}
/**
* Sentinel returned during SSR when `window` is unavailable. "use client"
* components in the Next.js App Router are server-side rendered on the
* initial request (that's how the HTML is streamed before hydration),
* which means their function bodies execute on the server too. We can't
* throw here without breaking SSR — instead we return a placeholder, and
* post-hydration the next render reads the real values out of
* window.__SHOWCASE_CONFIG__. Server components that need the live env
* values MUST import getRuntimeConfig from runtime-config.ts (the server
* variant), not this file.
*
* URL fields use a parseable `https://ssr-placeholder.invalid/` sentinel
* — NOT the empty string — because consumer components call
* `new URL(cfg.someUrl)` inline during render, and `new URL("")` throws
* a TypeError that escapes the SSR response as a 500. The `.invalid`
* TLD is reserved by RFC 2606 so the URL also can't accidentally
* resolve. The post-hydration re-read swaps in the real value and the
* href is fixed up before any user interaction (consumers also use
* `suppressHydrationWarning` to silence the benign href diff).
*
* Analytics-KEY fields STAY the empty string because every consumer
* gates side-effects on `if (key)` truthiness — populating them with a
* sentinel would erroneously start analytics during SSR.
*/
const SSR_PLACEHOLDER_URL = "https://ssr-placeholder.invalid/";
const SSR_PLACEHOLDER: RuntimeConfig = {
baseUrl: SSR_PLACEHOLDER_URL,
shellUrl: SSR_PLACEHOLDER_URL,
intelligenceSignupUrl: SSR_PLACEHOLDER_URL,
posthogKey: "",
posthogHost: SSR_PLACEHOLDER_URL,
scarfPixelId: "",
googleAnalyticsTrackingId: "",
reb2bKey: "",
reoKey: "",
};
/**
* Returns the runtime config injected by the root server layout.
*
* During SSR (no window) returns a sentinel placeholder; client code
* re-reads after hydration and gets the real values. If the inline
* <script> never runs (a route bypassed the layout, or injection ran
* with empty inputs), the post-hydration read throws — surfacing the
* wiring bug loudly rather than silently rendering empty URLs.
*/
export function getRuntimeConfig(): RuntimeConfig {
if (typeof window === "undefined") {
// SSR phase — "use client" component bodies execute here too.
// Return placeholder; hydration will re-render with real values.
return SSR_PLACEHOLDER;
}
const cfg = window.__SHOWCASE_CONFIG__;
if (!cfg) {
// The root layout always emits the <script> tag, so a missing
// value here is a wiring bug (e.g. a route bypassed the layout,
// or the injection script ran with empty inputs). Surface it
// loudly rather than silently returning empty strings.
throw new Error(
"[runtime-config.client] window.__SHOWCASE_CONFIG__ is missing. " +
"The root layout must inject runtime config before client mount.",
);
}
return cfg;
}