mirror of
https://github.com/CopilotKit/CopilotKit.git
synced 2026-09-14 16:26:20 +08:00
28f33ecc8a
Six fixes addressing CR findings on the Option-B runtime URL-injection migration:
1. SSR_PLACEHOLDER must be parseable URL sentinels — `new URL("")` throws on
SSR causing 500s for any consumer that constructs URLs from runtime-config
fields. Use `.invalid`-TLD sentinels (RFC 2606) for URL fields; analytics
keys stay empty string. Add `suppressHydrationWarning` on consumers that
render the placeholder server-side and the real value post-hydration
(integration-grid, page-actions popover).
2. Hook-order: move `usePathname()`/`useEffect` ABOVE the early-return in
use-google-analytics. Gate the effect bodies on `GA_ID` instead so React
sees a stable hook order across renders.
3. `readUrl`/`readKey` accept either bare or `NEXT_PUBLIC_*`-prefixed env
names via a fallback chain — covers both server-only and inlined-public
variable conventions without forcing a rename across deploy targets.
4. Extract `serializeRuntimeConfig` to `lib/runtime-config-serialize.ts` so
the OWASP-escape behavior (XSS via </script>, U+2028/U+2029 line-terminator
injection) can be unit-tested without importing the layout into vitest.
5. Reclassify `intelligenceSignupUrl`/`posthogHost` from FATAL-CONFIG to
info-level in shell-docs — these are optional integrations, not hard
wiring failures, so absence should not poison the error stream.
6. Comment-rot cleanup: drop "Option B", B12, "the bug we are fixing", fix
"four substrings"→"three substrings" miscounts, and refresh shell-docs
.env.example to describe the runtime-injection contract instead of a
stale next.config throw claim.
V1: shell + shell-docs `next build` succeeds (no Edge-runtime crash on
`unstable_noStore`).
V2: `OPS_BASE_URL=` shell-dashboard `next build` no longer throws —
`next.config.ts` is now a phase-aware function that emits a sentinel
destination at build time and throws only at start (PHASE_PRODUCTION_BUILD
from next/constants).
Tests: shell-docs 72/72, shell 12/12, shell-dashboard runtime-config 16/16
(pre-existing baseline-partner-count failure unchanged).
65 lines
2.8 KiB
TypeScript
65 lines
2.8 KiB
TypeScript
import type { NextConfig } from "next";
|
|
import { PHASE_PRODUCTION_BUILD } from "next/constants";
|
|
|
|
/**
|
|
* Next.js config for the dashboard shell.
|
|
*
|
|
* The Status tab calls the showcase-harness HTTP API at the relative path
|
|
* `/api/ops/*`. There is no /api/ops route handler in this app — the path
|
|
* is a deterministic same-origin proxy that this rewrite forwards to the
|
|
* real showcase-harness service. Going same-origin sidesteps two production
|
|
* blockers:
|
|
* 1. showcase-harness has no CORS allowlist for cross-origin browser calls.
|
|
* 2. We don't want the ops base URL inlined into the client bundle (it
|
|
* would also force `NEXT_PUBLIC_*` exposure semantics).
|
|
*
|
|
* `OPS_BASE_URL` is required at START — not at build. `next build`
|
|
* evaluates `rewrites()` so route metadata can be persisted into the
|
|
* artifact, AND `next start` evaluates it again at process boot.
|
|
* Throwing at build time would prevent the runtime-injection deploy
|
|
* pattern (single artifact, env supplied at start). So when invoked
|
|
* under the build phase we accept the absence with a sentinel
|
|
* destination — the rewrite is reconstructed with the real env value
|
|
* at start. At start time, missing OPS_BASE_URL still throws loudly to
|
|
* surface the wiring bug.
|
|
*
|
|
* The config is exported as a function `(phase) => NextConfig` so the
|
|
* build-vs-start distinction is reliable without relying on the
|
|
* `NEXT_PHASE` env (which Next.js does not always export to user code).
|
|
*/
|
|
const SENTINEL_OPS_BASE = "http://ops.invalid";
|
|
|
|
export default function nextConfig(phase: string): NextConfig {
|
|
const isBuildPhase = phase === PHASE_PRODUCTION_BUILD;
|
|
return {
|
|
async rewrites() {
|
|
const opsBase = process.env.OPS_BASE_URL;
|
|
if (!opsBase) {
|
|
if (isBuildPhase) {
|
|
// Build phase: emit a parseable sentinel destination so
|
|
// `next build` succeeds. `rewrites()` runs again at
|
|
// `next start` with the real env value (or throws below).
|
|
return [
|
|
{
|
|
source: "/api/ops/:path*",
|
|
destination: `${SENTINEL_OPS_BASE}/api/:path*`,
|
|
},
|
|
];
|
|
}
|
|
throw new Error(
|
|
"OPS_BASE_URL must be set on this Railway service — see showcase/RAILWAY.md " +
|
|
"(without it, /api/ops/* requests cannot proxy to showcase-harness)",
|
|
);
|
|
}
|
|
// Strip trailing slashes so we never produce `https://host//api/...`
|
|
// (some servers reject the double slash). Mirrors the same
|
|
// normalization in `src/lib/ops-api.ts:resolveBaseUrl` so the
|
|
// server-side rewrite and client-side fetch agree on the URL shape.
|
|
const normalized = opsBase.replace(/\/+$/, "");
|
|
return [
|
|
{ source: "/api/ops/:path*", destination: `${normalized}/api/:path*` },
|
|
];
|
|
},
|
|
};
|
|
}
|