diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 2e41e3b20..ad5c734a4 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -8,6 +8,7 @@ import "./globals.css";
import { Providers } from "@/components/providers";
import { AuthShell } from "@/components/layout/auth-shell";
import { MonitoringBootstrap } from "@/components/monitoring/bootstrap";
+import { getPublicMonitoringSettings } from "@/lib/monitoring-settings";
import { WebVitalsReporter } from "@/components/monitoring/web-vitals-reporter";
import { resolveInitialLocale } from "@/lib/i18n/resolve-initial-locale";
import { isKeyMismatch } from "@/lib/boot/key-mismatch-state";
@@ -86,7 +87,12 @@ export default async function RootLayout({
? ((await headers()).get("x-nonce") ?? undefined)
: undefined;
- const initialLocale = await resolveInitialLocale();
+ // The monitoring switches ride the document render instead of a request
+ // from every page load; the key-mismatch page reads no data.
+ const [initialLocale, monitoring] = await Promise.all([
+ resolveInitialLocale(),
+ isKeyMismatch() ? null : getPublicMonitoringSettings(),
+ ]);
return (
@@ -139,7 +145,15 @@ export default async function RootLayout({
) : (
-
+
{/* `DEMO_MODE` is a server-only env var; the proxy uses it to
block mutations. Resolve it here (server component) and
diff --git a/src/components/monitoring/__tests__/bootstrap.test.tsx b/src/components/monitoring/__tests__/bootstrap.test.tsx
new file mode 100644
index 000000000..e0a8c8fdb
--- /dev/null
+++ b/src/components/monitoring/__tests__/bootstrap.test.tsx
@@ -0,0 +1,49 @@
+import { describe, expect, it, vi } from "vitest";
+import { renderToStaticMarkup } from "react-dom/server";
+
+/**
+ * The monitoring switches come from the root layout's render, not from a
+ * request every page load sent to `/api/monitoring/settings` for three
+ * instance-wide values. The first render already mounts what they turn on.
+ */
+
+const apiGet = vi.fn();
+vi.mock("@/lib/api/api-fetch", () => ({
+ apiGet: (...args: unknown[]) => apiGet(...args),
+}));
+vi.mock("@/components/monitoring/umami-script", () => ({
+ UmamiScript: (props: { enabled: boolean; websiteId: string | null }) => (
+
+ ),
+}));
+vi.mock("@/components/monitoring/glitchtip-reporter", () => ({
+ GlitchtipReporter: (props: { enabled: boolean }) => (
+
+ ),
+}));
+
+import { MonitoringBootstrap } from "../bootstrap";
+
+describe("MonitoringBootstrap", () => {
+ it("mounts what the layout's switches turn on, without asking the server", () => {
+ const html = renderToStaticMarkup(
+ ,
+ );
+
+ expect(html).toContain('data-umami="true:site-1"');
+ expect(html).toContain('data-glitchtip="true"');
+ expect(apiGet).not.toHaveBeenCalled();
+ });
+
+ it("mounts nothing without settings", () => {
+ expect(renderToStaticMarkup()).toBe(
+ "",
+ );
+ });
+});
diff --git a/src/components/monitoring/bootstrap.tsx b/src/components/monitoring/bootstrap.tsx
index 0598dcff5..1f6c790ae 100644
--- a/src/components/monitoring/bootstrap.tsx
+++ b/src/components/monitoring/bootstrap.tsx
@@ -1,43 +1,21 @@
-"use client";
-
-import { useEffect, useState } from "react";
import { UmamiScript } from "@/components/monitoring/umami-script";
import { GlitchtipReporter } from "@/components/monitoring/glitchtip-reporter";
-import { apiGet } from "@/lib/api/api-fetch";
-
-interface MonitoringSettings {
- umamiEnabled: boolean;
- umamiWebsiteId: string | null;
- glitchtipEnabled: boolean;
-}
-
-export function MonitoringBootstrap() {
- const [settings, setSettings] = useState(null);
-
- useEffect(() => {
- let active = true;
-
- apiGet("/api/monitoring/settings", {
- cache: "no-store",
- })
- .then((data) => {
- if (!active || !data) return;
- setSettings(data);
- })
- .catch(() => {
- if (!active) return;
- setSettings({
- umamiEnabled: false,
- umamiWebsiteId: null,
- glitchtipEnabled: false,
- });
- });
-
- return () => {
- active = false;
- };
- }, []);
+import type { PublicMonitoringSettings } from "@/lib/monitoring-settings";
+/**
+ * Mounts the operator's analytics script and error reporter. The root layout
+ * reads the instance-wide switches on the server and passes them in, so no
+ * page load asks `/api/monitoring/settings` for three values the document
+ * render already had. `null` (the key-mismatch page) mounts nothing.
+ */
+export function MonitoringBootstrap({
+ settings,
+}: {
+ settings: Pick<
+ PublicMonitoringSettings,
+ "umamiEnabled" | "umamiWebsiteId" | "glitchtipEnabled"
+ > | null;
+}) {
if (!settings) return null;
return (