Skip to content

Commit dcd3c16

Browse files
committed
feat(showcase): route shell-docs analytics + middleware through runtime config
Migrate the remaining shell-docs consumers of NEXT_PUBLIC_* env vars off of process.env reads and onto the runtime-config readers: - lib/providers/posthog-provider.tsx (client): the PostHog key is pulled inside PostHogProvider so the value reflects the current deploy's NEXT_PUBLIC_POSTHOG_KEY; empty string disables analytics via the existing truthiness gates around init/capture - lib/providers/scarf-pixel.tsx (client): pixel id read at render time; empty string returns null (existing no-op behavior preserved) - lib/hooks/use-google-analytics.tsx (client): GA tracking id read at hook invocation; empty string short-circuits via the existing `if (!GA_ID) return` guard - middleware.ts (Edge): POSTHOG_HOST is now resolved per-request via getRuntimeConfigEdge() (the Edge wrapper skips unstable_noStore() since next/cache is unavailable there and middleware always runs per-request anyway). Server-side POSTHOG_KEY is unchanged — it's not a NEXT_PUBLIC_* var.
1 parent e7ab07a commit dcd3c16

4 files changed

Lines changed: 27 additions & 7 deletions

File tree

showcase/shell-docs/src/lib/hooks/use-google-analytics.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,13 @@ import { usePathname } from "next/navigation";
44
import { useEffect } from "react";
55
import ReactGA from "react-ga4";
66
import { normalizePathnameForAnalytics } from "@/lib/analytics-utils";
7+
import { getRuntimeConfig } from "@/lib/runtime-config.client";
78

89
export function useGoogleAnalytics() {
9-
const GA_ID = process.env.NEXT_PUBLIC_GOOGLE_ANALYTICS_TRACKING_ID;
10+
// Tracking ID is read at render time from the runtime config injected
11+
// by the root layout. Empty string disables GA — the early return
12+
// below already gates on truthiness.
13+
const GA_ID = getRuntimeConfig().googleAnalyticsTrackingId;
1014

1115
if (!GA_ID) {
1216
return;

showcase/shell-docs/src/lib/providers/posthog-provider.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import { PostHogProvider as PostHogProviderBase } from "posthog-js/react";
44
import posthog from "posthog-js";
55
import { useEffect, useState, useRef } from "react";
66
import { usePathname } from "next/navigation";
7+
import { getRuntimeConfig } from "@/lib/runtime-config.client";
78

8-
const POSTHOG_KEY = process.env.NEXT_PUBLIC_POSTHOG_KEY;
99
// Reverse-proxied via /ingest/* rewrites in next.config.ts so requests
1010
// flow through the docs host instead of *.i.posthog.com — bypasses
1111
// ad blockers / tracking-protection that target the PostHog hostname.
@@ -26,6 +26,11 @@ export function PostHogProvider({ children }: { children: React.ReactNode }) {
2626
const [sessionId, setSessionId] = useState<string | null>(null);
2727
const isInitializedRef = useRef(false);
2828

29+
// Pull the PostHog key from the runtime config injected by the root
30+
// layout — empty string means "analytics disabled in this env" and
31+
// the init/capture branches below already gate on truthiness.
32+
const POSTHOG_KEY = getRuntimeConfig().posthogKey;
33+
2934
// Read session_id from URL only on client side to avoid hydration mismatch
3035
useEffect(() => {
3136
if (typeof window !== "undefined") {

showcase/shell-docs/src/lib/providers/scarf-pixel.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
"use client";
22

33
import React from "react";
4+
import { getRuntimeConfig } from "@/lib/runtime-config.client";
45

56
export function ScarfPixel() {
6-
const SCARF_PIXEL_ID = process.env.NEXT_PUBLIC_SCARF_PIXEL_ID;
7+
// Pixel ID from the runtime config injected by the root layout —
8+
// empty string disables the pixel (consumer no-ops on falsy).
9+
const SCARF_PIXEL_ID = getRuntimeConfig().scarfPixelId;
710
if (!SCARF_PIXEL_ID) return null;
811
return (
912
<img

showcase/shell-docs/src/middleware.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { NextResponse } from "next/server";
22
import type { NextFetchEvent, NextRequest } from "next/server";
33
import { seoRedirects } from "@/lib/seo-redirects";
44
import { stripRouteGroupSegmentsFromPathname } from "@/lib/route-groups";
5+
import { getRuntimeConfigEdge } from "@/lib/runtime-config";
56
import registry from "@/data/registry.json";
67

78
// ---------------------------------------------------------------------------
@@ -59,8 +60,15 @@ for (const entry of seoRedirects) {
5960
// PostHog tracking via fetch (Edge Runtime compatible — no posthog-node SDK)
6061
// ---------------------------------------------------------------------------
6162

62-
const POSTHOG_HOST =
63-
process.env.NEXT_PUBLIC_POSTHOG_HOST ?? "https://eu.i.posthog.com";
63+
// PostHog host is read at REQUEST time from the Edge runtime-config
64+
// reader (which is the same body as the Node reader but skips the
65+
// `unstable_noStore()` call that the Edge bundle can't import). This
66+
// keeps a single built artifact retargetable across Railway envs by
67+
// changing NEXT_PUBLIC_POSTHOG_HOST. The reader applies the same
68+
// `https://eu.i.posthog.com` fallback used before.
69+
function getPosthogHost(): string {
70+
return getRuntimeConfigEdge().posthogHost;
71+
}
6472
const DISTINCT_ID_COOKIE = "ph_distinct_id";
6573
// ~2 years — long enough to meaningfully track returning visitors.
6674
const DISTINCT_ID_COOKIE_MAX_AGE = 60 * 60 * 24 * 365 * 2;
@@ -79,7 +87,7 @@ function trackRedirect(id: string, fromPath: string, toPath: string): void {
7987
}
8088

8189
// Fire-and-forget — don't await
82-
fetch(`${POSTHOG_HOST}/capture/`, {
90+
fetch(`${getPosthogHost()}/capture/`, {
8391
method: "POST",
8492
headers: { "Content-Type": "application/json" },
8593
body: JSON.stringify({
@@ -106,7 +114,7 @@ async function capturePageView(
106114
}
107115

108116
try {
109-
const response = await fetch(`${POSTHOG_HOST}/capture/`, {
117+
const response = await fetch(`${getPosthogHost()}/capture/`, {
110118
method: "POST",
111119
headers: { "Content-Type": "application/json" },
112120
body: JSON.stringify({

0 commit comments

Comments
 (0)