diff --git a/apps/web/Dockerfile b/apps/web/Dockerfile
index 8c275d1..1081449 100644
--- a/apps/web/Dockerfile
+++ b/apps/web/Dockerfile
@@ -17,7 +17,17 @@ FROM base AS builder
ENV NEXT_TELEMETRY_DISABLED=1
ARG NEXT_PUBLIC_AUTH_PROVIDER
+ARG NEXT_PUBLIC_CONVEX_URL
+ARG NEXT_PUBLIC_CLAWE_EDITION
+ARG NEXT_PUBLIC_COGNITO_USER_POOL_ID
+ARG NEXT_PUBLIC_COGNITO_CLIENT_ID
+ARG NEXT_PUBLIC_COGNITO_DOMAIN
ENV NEXT_PUBLIC_AUTH_PROVIDER=${NEXT_PUBLIC_AUTH_PROVIDER}
+ENV NEXT_PUBLIC_CONVEX_URL=${NEXT_PUBLIC_CONVEX_URL}
+ENV NEXT_PUBLIC_CLAWE_EDITION=${NEXT_PUBLIC_CLAWE_EDITION}
+ENV NEXT_PUBLIC_COGNITO_USER_POOL_ID=${NEXT_PUBLIC_COGNITO_USER_POOL_ID}
+ENV NEXT_PUBLIC_COGNITO_CLIENT_ID=${NEXT_PUBLIC_COGNITO_CLIENT_ID}
+ENV NEXT_PUBLIC_COGNITO_DOMAIN=${NEXT_PUBLIC_COGNITO_DOMAIN}
COPY --from=deps /app/ .
COPY --from=pruner /app/out/full/ .
diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx
index 2cb9734..39f9368 100644
--- a/apps/web/src/app/layout.tsx
+++ b/apps/web/src/app/layout.tsx
@@ -9,6 +9,7 @@ import { QueryProvider } from "@/providers/query-provider";
import { ThemeProvider } from "@/providers/theme-provider";
import { ApiClientProvider } from "@/providers/api-client-provider";
import { Toaster } from "@clawe/ui/components/sonner";
+import { RuntimeConfig } from "@/components/runtime-config";
const geistSans = localFont({
src: "./fonts/GeistVF.woff",
@@ -42,6 +43,7 @@ export default function RootLayout({
+
diff --git a/apps/web/src/components/runtime-config.tsx b/apps/web/src/components/runtime-config.tsx
new file mode 100644
index 0000000..703773e
--- /dev/null
+++ b/apps/web/src/components/runtime-config.tsx
@@ -0,0 +1,19 @@
+export const RuntimeConfig = () => {
+ // Cloud builds have NEXT_PUBLIC_CONVEX_URL baked in at build time — no runtime injection needed
+ if (process.env.NEXT_PUBLIC_CLAWE_EDITION === "cloud") {
+ return null;
+ }
+
+ const config = {
+ convexUrl:
+ process.env.NEXT_PUBLIC_CONVEX_URL || process.env.CONVEX_URL || "",
+ };
+
+ return (
+
+ );
+};
diff --git a/apps/web/src/lib/config.ts b/apps/web/src/lib/config.ts
index e19c012..ce62a5b 100644
--- a/apps/web/src/lib/config.ts
+++ b/apps/web/src/lib/config.ts
@@ -1,7 +1,11 @@
+import { getConvexUrl } from "@/lib/runtime-config";
+
export const config = {
isCloud: process.env.NEXT_PUBLIC_CLAWE_EDITION === "cloud",
authProvider: (process.env.NEXT_PUBLIC_AUTH_PROVIDER ?? "nextauth") as
| "nextauth"
| "cognito",
- convexUrl: process.env.NEXT_PUBLIC_CONVEX_URL ?? "",
+ get convexUrl() {
+ return getConvexUrl();
+ },
} as const;
diff --git a/apps/web/src/lib/runtime-config.ts b/apps/web/src/lib/runtime-config.ts
new file mode 100644
index 0000000..75e14e1
--- /dev/null
+++ b/apps/web/src/lib/runtime-config.ts
@@ -0,0 +1,23 @@
+interface ClaweConfig {
+ convexUrl: string;
+}
+
+declare global {
+ interface Window {
+ __CLAWE_CONFIG__?: ClaweConfig;
+ }
+}
+
+export function getConvexUrl(): string {
+ // Server-side: read env directly
+ if (typeof window === "undefined") {
+ return process.env.NEXT_PUBLIC_CONVEX_URL || process.env.CONVEX_URL || "";
+ }
+
+ // Client-side: read from injected script tag, fall back to build-time env
+ return (
+ window.__CLAWE_CONFIG__?.convexUrl ||
+ process.env.NEXT_PUBLIC_CONVEX_URL ||
+ ""
+ );
+}
diff --git a/apps/web/src/providers/convex-provider.tsx b/apps/web/src/providers/convex-provider.tsx
index cbd50ae..15ccac5 100644
--- a/apps/web/src/providers/convex-provider.tsx
+++ b/apps/web/src/providers/convex-provider.tsx
@@ -1,16 +1,19 @@
"use client";
import { ConvexProviderWithAuth, ConvexReactClient } from "convex/react";
-import type { ReactNode } from "react";
+import { useMemo, type ReactNode } from "react";
import { useConvexAuth } from "@/providers/auth-provider";
+import { getConvexUrl } from "@/lib/runtime-config";
-// Fallback URL for build time - won't be called during static generation
-const convex = new ConvexReactClient(
- process.env.NEXT_PUBLIC_CONVEX_URL || "http://localhost:0",
-);
+export const ConvexClientProvider = ({ children }: { children: ReactNode }) => {
+ const client = useMemo(
+ () => new ConvexReactClient(getConvexUrl() || "http://localhost:0"),
+ [],
+ );
-export const ConvexClientProvider = ({ children }: { children: ReactNode }) => (
-
- {children}
-
-);
+ return (
+
+ {children}
+
+ );
+};