diff --git a/surfsense_web/.env.example b/surfsense_web/.env.example
index 11646c948..7d03cf498 100644
--- a/surfsense_web/.env.example
+++ b/surfsense_web/.env.example
@@ -14,7 +14,10 @@ SURFSENSE_BACKEND_INTERNAL_URL=http://backend:8000
# ─────────────────────────────────────────────────────────────────────────────
# Runtime configuration (read at runtime by the server, no rebuild needed)
# ─────────────────────────────────────────────────────────────────────────────
-
+# Configure these plain variables for runtime behavior. They are read by server
+# code when the app starts/serves requests, so changing them requires restarting
+# the web process but not rebuilding the frontend bundle.
+#
# Authentication method: LOCAL (email/password) or GOOGLE (OAuth).
AUTH_TYPE=LOCAL
# Document parsing backend: DOCLING, LLAMACLOUD, etc.
@@ -22,16 +25,6 @@ ETL_SERVICE=DOCLING
# Deployment mode: self-hosted or cloud.
DEPLOYMENT_MODE=self-hosted
-# ─────────────────────────────────────────────────────────────────────────────
-# Build-time fallbacks for packaged clients (e.g. Electron) without a runtime
-# config provider. Optional; Docker reads the plain runtime vars above first.
-# ─────────────────────────────────────────────────────────────────────────────
-# NEXT_PUBLIC_AUTH_TYPE=GOOGLE
-# NEXT_PUBLIC_ETL_SERVICE=DOCLING
-# NEXT_PUBLIC_DEPLOYMENT_MODE=self-hosted
-# Overrides the app version shown in the UI (defaults to package.json version).
-# NEXT_PUBLIC_APP_VERSION=
-
# ─────────────────────────────────────────────────────────────────────────────
# Database (Contact Form, optional)
# ─────────────────────────────────────────────────────────────────────────────
@@ -72,3 +65,20 @@ NEXT_PUBLIC_GOOGLE_ADSENSE_SLOT_FREE_HUB_BEFORE_FAQ=
# ─────────────────────────────────────────────────────────────────────────────
NEXT_PUBLIC_GLOBAL_ANNOUNCEMENT_ENABLED=false
NEXT_PUBLIC_GLOBAL_ANNOUNCEMENT_MESSAGE=
+
+# ─────────────────────────────────────────────────────────────────────────────
+# Internal build-time fallbacks
+# ─────────────────────────────────────────────────────────────────────────────
+#
+# Most deployments should leave these unset.
+#
+# These are only for SurfSense-managed production/cloud builds or packaged
+# clients that do not have the normal server runtime config available.
+#
+# NEXT_PUBLIC_* values are embedded into the browser bundle during `next build`.
+# Changing them after the bundle is built has no effect.
+
+# NEXT_PUBLIC_AUTH_TYPE=GOOGLE
+# NEXT_PUBLIC_ETL_SERVICE=DOCLING
+# NEXT_PUBLIC_DEPLOYMENT_MODE=self-hosted
+# NEXT_PUBLIC_APP_VERSION=
\ No newline at end of file
diff --git a/surfsense_web/app/globals.css b/surfsense_web/app/globals.css
index 3cdb34bff..4a29edfa6 100644
--- a/surfsense_web/app/globals.css
+++ b/surfsense_web/app/globals.css
@@ -58,6 +58,11 @@
--highlight: oklch(0.852 0.199 91.936);
}
+html[data-surfsense-auth-type="GOOGLE"] .runtime-auth-local,
+html[data-surfsense-auth-type="LOCAL"] .runtime-auth-google {
+ display: none;
+}
+
.dark {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
diff --git a/surfsense_web/app/layout.tsx b/surfsense_web/app/layout.tsx
index 1e9c9eebe..46182f40e 100644
--- a/surfsense_web/app/layout.tsx
+++ b/surfsense_web/app/layout.tsx
@@ -2,6 +2,7 @@ import type { Metadata, Viewport } from "next";
import "./globals.css";
import { RootProvider } from "fumadocs-ui/provider/next";
import { Roboto } from "next/font/google";
+import Script from "next/script";
import { AnnouncementToastProvider } from "@/components/announcements/AnnouncementToastProvider";
import { DesktopUpdateToast } from "@/components/desktop/desktop-update-toast";
import { GlobalLoadingProvider } from "@/components/providers/GlobalLoadingProvider";
@@ -16,8 +17,13 @@ import {
import { ThemeProvider } from "@/components/theme/theme-provider";
import { Toaster } from "@/components/ui/sonner";
import { LocaleProvider } from "@/contexts/LocaleContext";
+import { BUILD_TIME_AUTH_TYPE } from "@/lib/env-config";
import { PlatformProvider } from "@/contexts/platform-context";
import { ReactQueryClientProvider } from "@/lib/query-client/query-client.provider";
+import {
+ getRuntimeAuthInitScript,
+ resolveRuntimeAuthUiMode,
+} from "@/lib/runtime-auth-config";
import { cn } from "@/lib/utils";
const roboto = Roboto({
@@ -131,8 +137,15 @@ export default function RootLayout({
// Language can be switched dynamically through LanguageSwitcher component
// Locale state is managed by LocaleContext and persisted in localStorage
return (
-
+