From 60d540b1e53b67ab9beda275683ea6002a557433 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Tue, 5 May 2026 17:18:13 -0700 Subject: [PATCH 1/8] chore(deps): upgrade next.js to 16.2.4 - Bump next and @next/env to 16.2.4 across root, apps/sim, apps/docs - Replace next-runtime-env's env() helper (calls unstable_noStore(), rejected by Next 16.2 outside request scope) with a direct window.__ENV / process.env getter - Add export const dynamic = 'force-dynamic' on landing /privacy and /terms pages so NEXT_PUBLIC_* runtime env reads aren't baked at build --- apps/docs/package.json | 2 +- apps/sim/app/(landing)/privacy/page.tsx | 4 ++++ apps/sim/app/(landing)/terms/page.tsx | 4 ++++ apps/sim/lib/core/config/env.ts | 19 ++++++++++++----- apps/sim/next.config.ts | 10 ++++----- apps/sim/package.json | 6 +++--- bun.lock | 28 ++++++++++++------------- package.json | 4 ++-- 8 files changed, 47 insertions(+), 30 deletions(-) diff --git a/apps/docs/package.json b/apps/docs/package.json index 8c314fb40bf..b96c20d0535 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -26,7 +26,7 @@ "fumadocs-openapi": "10.3.13", "fumadocs-ui": "16.6.7", "lucide-react": "^0.511.0", - "next": "16.1.6", + "next": "16.2.4", "next-themes": "^0.4.6", "postgres": "^3.4.5", "react": "19.2.4", diff --git a/apps/sim/app/(landing)/privacy/page.tsx b/apps/sim/app/(landing)/privacy/page.tsx index 44140c62bb6..7ec450be5d8 100644 --- a/apps/sim/app/(landing)/privacy/page.tsx +++ b/apps/sim/app/(landing)/privacy/page.tsx @@ -3,6 +3,10 @@ import Link from 'next/link' import { getEnv } from '@/lib/core/config/env' import { ExternalRedirect, LegalLayout } from '@/app/(landing)/components' +/** Opt out of static prerendering so `NEXT_PUBLIC_PRIVACY_URL` is read from + * the live runtime environment (e.g. Docker-injected) rather than baked at build. */ +export const dynamic = 'force-dynamic' + export const metadata: Metadata = { title: 'Privacy Policy', description: diff --git a/apps/sim/app/(landing)/terms/page.tsx b/apps/sim/app/(landing)/terms/page.tsx index 08c16aeaa14..373b6cda800 100644 --- a/apps/sim/app/(landing)/terms/page.tsx +++ b/apps/sim/app/(landing)/terms/page.tsx @@ -3,6 +3,10 @@ import Link from 'next/link' import { getEnv } from '@/lib/core/config/env' import { ExternalRedirect, LegalLayout } from '@/app/(landing)/components' +/** Opt out of static prerendering so `NEXT_PUBLIC_TERMS_URL` is read from + * the live runtime environment (e.g. Docker-injected) rather than baked at build. */ +export const dynamic = 'force-dynamic' + export const metadata: Metadata = { title: 'Terms of Service', description: diff --git a/apps/sim/lib/core/config/env.ts b/apps/sim/lib/core/config/env.ts index 14bf33ce5d4..2c911f46c7a 100644 --- a/apps/sim/lib/core/config/env.ts +++ b/apps/sim/lib/core/config/env.ts @@ -1,14 +1,23 @@ import { createEnv } from '@t3-oss/env-nextjs' -import { env as runtimeEnv } from 'next-runtime-env' import { z } from 'zod' /** * Universal environment variable getter that works in both client and server contexts. - * - Client-side: Uses next-runtime-env for runtime injection (supports Docker runtime vars) - * - Server-side: Falls back to process.env when runtimeEnv returns undefined - * - Provides seamless Docker runtime variable support for NEXT_PUBLIC_ vars + * - Client-side: reads from `window.__ENV`, which `` from + * next-runtime-env populates before hydration (declared globally by that + * package). Supports Docker runtime injection of `NEXT_PUBLIC_*` vars + * without rebuilding. + * - Server-side: reads `process.env` directly. + * + * We deliberately do not import next-runtime-env's `env()` helper. It calls + * `unstable_noStore()` from `next/cache`, which Next 16.2+ rejects when + * invoked outside a request scope (e.g. during `next.config.ts` compilation + * or top-level module evaluation). */ -const getEnv = (variable: string) => runtimeEnv(variable) ?? process.env[variable] +const getEnv = (variable: string): string | undefined => { + if (typeof window === 'undefined') return process.env[variable] + return window.__ENV?.[variable] ?? process.env[variable] +} // biome-ignore format: keep alignment for readability export const env = createEnv({ diff --git a/apps/sim/next.config.ts b/apps/sim/next.config.ts index e349e2c865b..7a96c063cfe 100644 --- a/apps/sim/next.config.ts +++ b/apps/sim/next.config.ts @@ -1,5 +1,5 @@ import type { NextConfig } from 'next' -import { env, getEnv, isTruthy } from './lib/core/config/env' +import { env, isTruthy } from './lib/core/config/env' import { isDev } from './lib/core/config/feature-flags' import { getChatEmbedCSPPolicy, @@ -40,13 +40,13 @@ const nextConfig: NextConfig = { hostname: 'lh3.googleusercontent.com', }, // Brand logo domain if configured - ...(getEnv('NEXT_PUBLIC_BRAND_LOGO_URL') + ...(process.env.NEXT_PUBLIC_BRAND_LOGO_URL ? (() => { try { return [ { protocol: 'https' as const, - hostname: new URL(getEnv('NEXT_PUBLIC_BRAND_LOGO_URL')!).hostname, + hostname: new URL(process.env.NEXT_PUBLIC_BRAND_LOGO_URL!).hostname, }, ] } catch { @@ -55,13 +55,13 @@ const nextConfig: NextConfig = { })() : []), // Brand favicon domain if configured - ...(getEnv('NEXT_PUBLIC_BRAND_FAVICON_URL') + ...(process.env.NEXT_PUBLIC_BRAND_FAVICON_URL ? (() => { try { return [ { protocol: 'https' as const, - hostname: new URL(getEnv('NEXT_PUBLIC_BRAND_FAVICON_URL')!).hostname, + hostname: new URL(process.env.NEXT_PUBLIC_BRAND_FAVICON_URL!).hostname, }, ] } catch { diff --git a/apps/sim/package.json b/apps/sim/package.json index 9d0c3e8a932..9eadf53bcd4 100644 --- a/apps/sim/package.json +++ b/apps/sim/package.json @@ -160,7 +160,7 @@ "mongodb": "6.19.0", "mysql2": "3.14.3", "neo4j-driver": "6.0.1", - "next": "16.1.6", + "next": "16.2.4", "next-mdx-remote": "^5.0.0", "next-runtime-env": "3.3.0", "next-themes": "^0.4.6", @@ -250,8 +250,8 @@ "sharp" ], "overrides": { - "next": "16.1.6", - "@next/env": "16.1.6", + "next": "16.2.4", + "@next/env": "16.2.4", "drizzle-orm": "^0.45.2", "postgres": "^3.4.5", "react-floater": { diff --git a/bun.lock b/bun.lock index f8b62029912..813bb69bdb8 100644 --- a/bun.lock +++ b/bun.lock @@ -29,7 +29,7 @@ "fumadocs-openapi": "10.3.13", "fumadocs-ui": "16.6.7", "lucide-react": "^0.511.0", - "next": "16.1.6", + "next": "16.2.4", "next-themes": "^0.4.6", "postgres": "^3.4.5", "react": "19.2.4", @@ -214,7 +214,7 @@ "mongodb": "6.19.0", "mysql2": "3.14.3", "neo4j-driver": "6.0.1", - "next": "16.1.6", + "next": "16.2.4", "next-mdx-remote": "^5.0.0", "next-runtime-env": "3.3.0", "next-themes": "^0.4.6", @@ -483,9 +483,9 @@ "sharp", ], "overrides": { - "@next/env": "16.1.6", + "@next/env": "16.2.4", "drizzle-orm": "^0.45.2", - "next": "16.1.6", + "next": "16.2.4", "postgres": "^3.4.5", "react": "19.2.4", "react-dom": "19.2.4", @@ -1055,23 +1055,23 @@ "@napi-rs/canvas-win32-x64-msvc": ["@napi-rs/canvas-win32-x64-msvc@0.1.100", "", { "os": "win32", "cpu": "x64" }, "sha512-MyT1j3mHC2+Lu4pBi9mKyMJhtP6U7k7EldY7sj/uS5gJA65gTXt8MefJQXLJo5d/vZbuWmfxzkEUNc/urV3pHA=="], - "@next/env": ["@next/env@16.1.6", "", {}, "sha512-N1ySLuZjnAtN3kFnwhAwPvZah8RJxKasD7x1f8shFqhncnWZn4JMfg37diLNuoHsLAlrDfM3g4mawVdtAG8XLQ=="], + "@next/env": ["@next/env@16.2.4", "", {}, "sha512-dKkkOzOSwFYe5RX6y26fZgkSpVAlIOJKQHIiydQcrWH6y/97+RceSOAdjZ14Qa3zLduVUy0TXcn+EiM6t4rPgw=="], - "@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.1.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-wTzYulosJr/6nFnqGW7FrG3jfUUlEf8UjGA0/pyypJl42ExdVgC6xJgcXQ+V8QFn6niSG2Pb8+MIG1mZr2vczw=="], + "@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-OXTFFox5EKN1Ym08vfrz+OXxmCcEjT4SFMbNRsWZE99dMqt2Kcusl5MqPXcW232RYkMLQTy0hqgAMEsfEd/l2A=="], - "@next/swc-darwin-x64": ["@next/swc-darwin-x64@16.1.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-BLFPYPDO+MNJsiDWbeVzqvYd4NyuRrEYVB5k2N3JfWncuHAy2IVwMAOlVQDFjj+krkWzhY2apvmekMkfQR0CUQ=="], + "@next/swc-darwin-x64": ["@next/swc-darwin-x64@16.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-XhpVnUfmYWvD3YrXu55XdcAkQtOnvaI6wtQa8fuF5fGoKoxIUZ0kWPtcOfqJEWngFF/lOS9l3+O9CcownhiQxQ=="], - "@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@16.1.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-OJYkCd5pj/QloBvoEcJ2XiMnlJkRv9idWA/j0ugSuA34gMT6f5b7vOiCQHVRpvStoZUknhl6/UxOXL4OwtdaBw=="], + "@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@16.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-Mx/tjlNA3G8kg14QvuGAJ4xBwPk1tUHq56JxZ8CXnZwz1Etz714soCEzGQQzVMz4bEnGPowzkV6Xrp6wAkEWOQ=="], - "@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@16.1.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-S4J2v+8tT3NIO9u2q+S0G5KdvNDjXfAv06OhfOzNDaBn5rw84DGXWndOEB7d5/x852A20sW1M56vhC/tRVbccQ=="], + "@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@16.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-iVMMp14514u7Nup2umQS03nT/bN9HurK8ufylC3FZNykrwjtx7V1A7+4kvhbDSCeonTVqV3Txnv0Lu+m2oDXNg=="], - "@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@16.1.6", "", { "os": "linux", "cpu": "x64" }, "sha512-2eEBDkFlMMNQnkTyPBhQOAyn2qMxyG2eE7GPH2WIDGEpEILcBPI/jdSv4t6xupSP+ot/jkfrCShLAa7+ZUPcJQ=="], + "@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@16.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-EZOvm1aQWgnI/N/xcWOlnS3RQBk0VtVav5Zo7n4p0A7UKyTDx047k8opDbXgBpHl4CulRqRfbw3QrX2w5UOXMQ=="], - "@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@16.1.6", "", { "os": "linux", "cpu": "x64" }, "sha512-oicJwRlyOoZXVlxmIMaTq7f8pN9QNbdes0q2FXfRsPhfCi8n8JmOZJm5oo1pwDaFbnnD421rVU409M3evFbIqg=="], + "@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@16.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-h9FxsngCm9cTBf71AR4fGznDEDx1hS7+kSEiIRjq5kO1oXWm07DxVGZjCvk0SGx7TSjlUqhI8oOyz7NfwAdPoA=="], - "@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@16.1.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-gQmm8izDTPgs+DCWH22kcDmuUp7NyiJgEl18bcr8irXA5N2m2O+JQIr6f3ct42GOs9c0h8QF3L5SzIxcYAAXXw=="], + "@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@16.2.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-3NdJV5OXMSOeJYijX+bjaLge3mJBlh4ybydbT4GFoB/2hAojWHtMhl3CYlYoMrjPuodp0nzFVi4Tj2+WaMg+Ow=="], - "@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.1.6", "", { "os": "win32", "cpu": "x64" }, "sha512-NRfO39AIrzBnixKbjuo2YiYhB6o9d8v/ymU9m/Xk8cyVk+k7XylniXkHwjs4s70wedVffc6bQNbufk5v0xEm0A=="], + "@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.2.4", "", { "os": "win32", "cpu": "x64" }, "sha512-kMVGgsqhO5YTYODD9IPGGhA6iprWidQckK3LmPeW08PIFENRmgfb4MjXHO+p//d+ts2rpjvK5gXWzXSMrPl9cw=="], "@noble/ciphers": ["@noble/ciphers@2.2.0", "", {}, "sha512-Z6pjIZ/8IJcCGzb2S/0Px5J81yij85xASuk1teLNeg75bfT07MV3a/O2Mtn1I2se43k3lkVEcFaR10N4cgQcZA=="], @@ -3237,7 +3237,7 @@ "netmask": ["netmask@2.1.1", "", {}, "sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA=="], - "next": ["next@16.1.6", "", { "dependencies": { "@next/env": "16.1.6", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.8.3", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.1.6", "@next/swc-darwin-x64": "16.1.6", "@next/swc-linux-arm64-gnu": "16.1.6", "@next/swc-linux-arm64-musl": "16.1.6", "@next/swc-linux-x64-gnu": "16.1.6", "@next/swc-linux-x64-musl": "16.1.6", "@next/swc-win32-arm64-msvc": "16.1.6", "@next/swc-win32-x64-msvc": "16.1.6", "sharp": "^0.34.4" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-hkyRkcu5x/41KoqnROkfTm2pZVbKxvbZRuNvKXLRXxs3VfyO0WhY50TQS40EuKO9SW3rBj/sF3WbVwDACeMZyw=="], + "next": ["next@16.2.4", "", { "dependencies": { "@next/env": "16.2.4", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.2.4", "@next/swc-darwin-x64": "16.2.4", "@next/swc-linux-arm64-gnu": "16.2.4", "@next/swc-linux-arm64-musl": "16.2.4", "@next/swc-linux-x64-gnu": "16.2.4", "@next/swc-linux-x64-musl": "16.2.4", "@next/swc-win32-arm64-msvc": "16.2.4", "@next/swc-win32-x64-msvc": "16.2.4", "sharp": "^0.34.5" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-kPvz56wF5frc+FxlHI5qnklCzbq53HTwORaWBGdT0vNoKh1Aya9XC8aPauH4NJxqtzbWsS5mAbctm4cr+EkQ2Q=="], "next-mdx-remote": ["next-mdx-remote@5.0.0", "", { "dependencies": { "@babel/code-frame": "^7.23.5", "@mdx-js/mdx": "^3.0.1", "@mdx-js/react": "^3.0.1", "unist-util-remove": "^3.1.0", "vfile": "^6.0.1", "vfile-matter": "^5.0.0" }, "peerDependencies": { "react": ">=16" } }, "sha512-RNNbqRpK9/dcIFZs/esQhuLA8jANqlH694yqoDBK8hkVdJUndzzGmnPHa2nyi90N4Z9VmzuSWNRpr5ItT3M7xQ=="], diff --git a/package.json b/package.json index 8e8b78e1802..eea53bc1299 100644 --- a/package.json +++ b/package.json @@ -48,8 +48,8 @@ "overrides": { "react": "19.2.4", "react-dom": "19.2.4", - "next": "16.1.6", - "@next/env": "16.1.6", + "next": "16.2.4", + "@next/env": "16.2.4", "drizzle-orm": "^0.45.2", "postgres": "^3.4.5" }, From dd08af0c43aebf2b67a0a51c4e44a2b66f2de2ba Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Tue, 5 May 2026 17:29:59 -0700 Subject: [PATCH 2/8] fix(whitelabel): force dynamic rendering for manifest.ts Without this, NEXT_PUBLIC_BRAND_* values are baked into the manifest at build time. Pairs with the next-runtime-env removal in the prior commit, restoring Docker runtime injection for whitelabel deployments. --- apps/sim/app/manifest.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/sim/app/manifest.ts b/apps/sim/app/manifest.ts index d66d2db1a17..67c73570882 100644 --- a/apps/sim/app/manifest.ts +++ b/apps/sim/app/manifest.ts @@ -1,6 +1,10 @@ import type { MetadataRoute } from 'next' import { getBrandConfig } from '@/ee/whitelabeling' +/** Opt out of static prerendering so `NEXT_PUBLIC_BRAND_*` is read from + * the live runtime environment (e.g. Docker-injected) rather than baked at build. */ +export const dynamic = 'force-dynamic' + export default function manifest(): MetadataRoute.Manifest { const brand = getBrandConfig() From f40fa93c22ae2e9193be2dc0555e80a8f9241546 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Tue, 5 May 2026 17:40:29 -0700 Subject: [PATCH 3/8] fix(oauth): wrap consent page useSearchParams in Suspense Next 16.2's stricter prerender check fails the build when useSearchParams() is used without a Suspense boundary. Splits the client component into an outer wrapper and inner body. --- apps/sim/app/(auth)/oauth/consent/page.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/apps/sim/app/(auth)/oauth/consent/page.tsx b/apps/sim/app/(auth)/oauth/consent/page.tsx index 8addf4e82b4..ea624d9b87f 100644 --- a/apps/sim/app/(auth)/oauth/consent/page.tsx +++ b/apps/sim/app/(auth)/oauth/consent/page.tsx @@ -1,6 +1,6 @@ 'use client' -import { useCallback, useEffect, useState } from 'react' +import { Suspense, useCallback, useEffect, useState } from 'react' import { ArrowLeftRight } from 'lucide-react' import Image from 'next/image' import { useRouter, useSearchParams } from 'next/navigation' @@ -25,6 +25,14 @@ interface ClientInfo { } export default function OAuthConsentPage() { + return ( + + + + ) +} + +function OAuthConsentInner() { const router = useRouter() const searchParams = useSearchParams() const { data: session } = useSession() From b0602a29ecd40bc767ab798ec74d7c4623f0a113 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Tue, 5 May 2026 17:41:57 -0700 Subject: [PATCH 4/8] fix(whitelabel): force dynamic rendering for landing segment Client components in (landing) (e.g. Navbar) read NEXT_PUBLIC_BRAND_* via getEnv. Without this, SSR prerender would bake the build-time process.env values into HTML, mismatching window.__ENV after hydration in Docker runtime-env deployments. Cascades to all landing routes via the layout. --- apps/sim/app/(landing)/layout.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/sim/app/(landing)/layout.tsx b/apps/sim/app/(landing)/layout.tsx index 3b10895f16e..4611adc8a3f 100644 --- a/apps/sim/app/(landing)/layout.tsx +++ b/apps/sim/app/(landing)/layout.tsx @@ -16,6 +16,11 @@ export const metadata: Metadata = { }, } +/** Opt the landing segment out of static prerendering so client components like + * Navbar render with the live runtime env (e.g. Docker-injected `NEXT_PUBLIC_BRAND_*`) + * during SSR, avoiding hydration mismatches against `window.__ENV`. */ +export const dynamic = 'force-dynamic' + /** * Landing page route-group layout. * From e037cb8138e8d27e54a174d5ec0a6cf7a62ec08b Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Tue, 5 May 2026 17:45:23 -0700 Subject: [PATCH 5/8] revert(whitelabel): drop force-dynamic from landing layout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cascading force-dynamic neutered dynamicParams = false + generateStaticParams on /blog/[slug], /integrations/[slug], /models/[provider], /models/[provider]/[model] — killing static prerender for SEO-critical pages. The hydration concern only materializes for whitelabel Docker deployments where build-time and runtime NEXT_PUBLIC_BRAND_* differ; those deployments can set the vars at build instead. Keeping force-dynamic on /privacy, /terms, and /manifest where it actually matters. --- apps/sim/app/(landing)/layout.tsx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/apps/sim/app/(landing)/layout.tsx b/apps/sim/app/(landing)/layout.tsx index 4611adc8a3f..3b10895f16e 100644 --- a/apps/sim/app/(landing)/layout.tsx +++ b/apps/sim/app/(landing)/layout.tsx @@ -16,11 +16,6 @@ export const metadata: Metadata = { }, } -/** Opt the landing segment out of static prerendering so client components like - * Navbar render with the live runtime env (e.g. Docker-injected `NEXT_PUBLIC_BRAND_*`) - * during SSR, avoiding hydration mismatches against `window.__ENV`. */ -export const dynamic = 'force-dynamic' - /** * Landing page route-group layout. * From 24c4c775f8cc0751e0000426040686ca59fe9f6a Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Tue, 5 May 2026 17:53:55 -0700 Subject: [PATCH 6/8] fix(prerender): wrap useSearchParams callsites for Next 16.2 Next 16.2 fails the build when a client component using useSearchParams() is statically prerendered without a Suspense boundary. - Wrap landing Navbar in Suspense (imported by /oauth/consent and other pages) - Add force-dynamic to reset-password, invite/[id], and unsubscribe pages whose client bodies call useSearchParams --- apps/sim/app/(auth)/reset-password/page.tsx | 2 ++ .../(landing)/components/navbar/navbar.tsx | 20 +++++++++++++++++-- apps/sim/app/invite/[id]/page.tsx | 2 ++ apps/sim/app/unsubscribe/page.tsx | 2 ++ 4 files changed, 24 insertions(+), 2 deletions(-) diff --git a/apps/sim/app/(auth)/reset-password/page.tsx b/apps/sim/app/(auth)/reset-password/page.tsx index cb6470bba0e..8d9ee5dfd42 100644 --- a/apps/sim/app/(auth)/reset-password/page.tsx +++ b/apps/sim/app/(auth)/reset-password/page.tsx @@ -5,4 +5,6 @@ export const metadata: Metadata = { title: 'Reset Password', } +export const dynamic = 'force-dynamic' + export default ResetPasswordPage diff --git a/apps/sim/app/(landing)/components/navbar/navbar.tsx b/apps/sim/app/(landing)/components/navbar/navbar.tsx index 4ee2fdb2aca..76dc8142ba2 100644 --- a/apps/sim/app/(landing)/components/navbar/navbar.tsx +++ b/apps/sim/app/(landing)/components/navbar/navbar.tsx @@ -1,6 +1,14 @@ 'use client' -import { useCallback, useContext, useEffect, useRef, useState, useSyncExternalStore } from 'react' +import { + Suspense, + useCallback, + useContext, + useEffect, + useRef, + useState, + useSyncExternalStore, +} from 'react' import dynamic from 'next/dynamic' import Image from 'next/image' import Link from 'next/link' @@ -50,7 +58,15 @@ interface NavbarProps { blogPosts?: NavBlogPost[] } -export default function Navbar({ logoOnly = false, blogPosts = [] }: NavbarProps) { +export default function Navbar(props: NavbarProps) { + return ( + + + + ) +} + +function NavbarInner({ logoOnly = false, blogPosts = [] }: NavbarProps) { const brand = getBrandConfig() const searchParams = useSearchParams() const sessionCtx = useContext(SessionContext) diff --git a/apps/sim/app/invite/[id]/page.tsx b/apps/sim/app/invite/[id]/page.tsx index e04a2ca7743..ac302790523 100644 --- a/apps/sim/app/invite/[id]/page.tsx +++ b/apps/sim/app/invite/[id]/page.tsx @@ -6,4 +6,6 @@ export const metadata: Metadata = { robots: { index: false }, } +export const dynamic = 'force-dynamic' + export default Invite diff --git a/apps/sim/app/unsubscribe/page.tsx b/apps/sim/app/unsubscribe/page.tsx index d1b3ec2de10..2258e81c93e 100644 --- a/apps/sim/app/unsubscribe/page.tsx +++ b/apps/sim/app/unsubscribe/page.tsx @@ -6,4 +6,6 @@ export const metadata: Metadata = { robots: { index: false }, } +export const dynamic = 'force-dynamic' + export default Unsubscribe From 3abdafc3d5b19275dc3a8ce61dc7263bdd38a185 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Tue, 5 May 2026 18:24:27 -0700 Subject: [PATCH 7/8] fix(navbar): preserve SSR HTML, drop Suspense bailout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reading useSearchParams() forced a Suspense fallback that emitted no navbar HTML during SSR — leaving crawlers and no-JS users without nav. The 'home' query param only affects client-side link targets, so read it from window.location in an effect after hydration. Restores full SSR navbar markup. --- .../(landing)/components/navbar/navbar.tsx | 27 +++++-------------- 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/apps/sim/app/(landing)/components/navbar/navbar.tsx b/apps/sim/app/(landing)/components/navbar/navbar.tsx index 76dc8142ba2..e5503dcb27e 100644 --- a/apps/sim/app/(landing)/components/navbar/navbar.tsx +++ b/apps/sim/app/(landing)/components/navbar/navbar.tsx @@ -1,18 +1,9 @@ 'use client' -import { - Suspense, - useCallback, - useContext, - useEffect, - useRef, - useState, - useSyncExternalStore, -} from 'react' +import { useCallback, useContext, useEffect, useRef, useState, useSyncExternalStore } from 'react' import dynamic from 'next/dynamic' import Image from 'next/image' import Link from 'next/link' -import { useSearchParams } from 'next/navigation' import { GithubOutlineIcon } from '@/components/icons' import { cn } from '@/lib/core/utils/cn' import { SessionContext } from '@/app/_shell/providers/session-provider' @@ -58,22 +49,16 @@ interface NavbarProps { blogPosts?: NavBlogPost[] } -export default function Navbar(props: NavbarProps) { - return ( - - - - ) -} - -function NavbarInner({ logoOnly = false, blogPosts = [] }: NavbarProps) { +export default function Navbar({ logoOnly = false, blogPosts = [] }: NavbarProps) { const brand = getBrandConfig() - const searchParams = useSearchParams() const sessionCtx = useContext(SessionContext) const session = sessionCtx?.data ?? null const isSessionPending = sessionCtx?.isPending ?? true const isAuthenticated = Boolean(session?.user?.id) - const isBrowsingHome = searchParams.has('home') + const [isBrowsingHome, setIsBrowsingHome] = useState(false) + useEffect(() => { + setIsBrowsingHome(new URLSearchParams(window.location.search).has('home')) + }, []) const useHomeLinks = isAuthenticated || isBrowsingHome const logoHref = useHomeLinks ? '/?home' : '/' const mounted = useSyncExternalStore( From de825d5ad6d678f02ef0285bed2537a9cb276490 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Tue, 5 May 2026 18:51:00 -0700 Subject: [PATCH 8/8] chore: trim verbose comments in next.js upgrade The force-dynamic export name is self-documenting; the remaining env.ts comment is tightened to the essential WHY (why we don't use next-runtime-env's helper). --- apps/sim/app/(landing)/privacy/page.tsx | 2 -- apps/sim/app/(landing)/terms/page.tsx | 2 -- apps/sim/app/manifest.ts | 2 -- apps/sim/lib/core/config/env.ts | 15 ++++----------- 4 files changed, 4 insertions(+), 17 deletions(-) diff --git a/apps/sim/app/(landing)/privacy/page.tsx b/apps/sim/app/(landing)/privacy/page.tsx index 7ec450be5d8..1081c090b82 100644 --- a/apps/sim/app/(landing)/privacy/page.tsx +++ b/apps/sim/app/(landing)/privacy/page.tsx @@ -3,8 +3,6 @@ import Link from 'next/link' import { getEnv } from '@/lib/core/config/env' import { ExternalRedirect, LegalLayout } from '@/app/(landing)/components' -/** Opt out of static prerendering so `NEXT_PUBLIC_PRIVACY_URL` is read from - * the live runtime environment (e.g. Docker-injected) rather than baked at build. */ export const dynamic = 'force-dynamic' export const metadata: Metadata = { diff --git a/apps/sim/app/(landing)/terms/page.tsx b/apps/sim/app/(landing)/terms/page.tsx index 373b6cda800..5d49f2bc52a 100644 --- a/apps/sim/app/(landing)/terms/page.tsx +++ b/apps/sim/app/(landing)/terms/page.tsx @@ -3,8 +3,6 @@ import Link from 'next/link' import { getEnv } from '@/lib/core/config/env' import { ExternalRedirect, LegalLayout } from '@/app/(landing)/components' -/** Opt out of static prerendering so `NEXT_PUBLIC_TERMS_URL` is read from - * the live runtime environment (e.g. Docker-injected) rather than baked at build. */ export const dynamic = 'force-dynamic' export const metadata: Metadata = { diff --git a/apps/sim/app/manifest.ts b/apps/sim/app/manifest.ts index 67c73570882..cb91437f3c1 100644 --- a/apps/sim/app/manifest.ts +++ b/apps/sim/app/manifest.ts @@ -1,8 +1,6 @@ import type { MetadataRoute } from 'next' import { getBrandConfig } from '@/ee/whitelabeling' -/** Opt out of static prerendering so `NEXT_PUBLIC_BRAND_*` is read from - * the live runtime environment (e.g. Docker-injected) rather than baked at build. */ export const dynamic = 'force-dynamic' export default function manifest(): MetadataRoute.Manifest { diff --git a/apps/sim/lib/core/config/env.ts b/apps/sim/lib/core/config/env.ts index 2c911f46c7a..36f21446765 100644 --- a/apps/sim/lib/core/config/env.ts +++ b/apps/sim/lib/core/config/env.ts @@ -2,17 +2,10 @@ import { createEnv } from '@t3-oss/env-nextjs' import { z } from 'zod' /** - * Universal environment variable getter that works in both client and server contexts. - * - Client-side: reads from `window.__ENV`, which `` from - * next-runtime-env populates before hydration (declared globally by that - * package). Supports Docker runtime injection of `NEXT_PUBLIC_*` vars - * without rebuilding. - * - Server-side: reads `process.env` directly. - * - * We deliberately do not import next-runtime-env's `env()` helper. It calls - * `unstable_noStore()` from `next/cache`, which Next 16.2+ rejects when - * invoked outside a request scope (e.g. during `next.config.ts` compilation - * or top-level module evaluation). + * Reads NEXT_PUBLIC_* env vars in both client and server contexts. + * Client reads `window.__ENV` (populated by ``); server reads `process.env`. + * We do not use next-runtime-env's `env()` helper because it calls `unstable_noStore()`, + * which Next 16.2+ rejects outside a request scope. */ const getEnv = (variable: string): string | undefined => { if (typeof window === 'undefined') return process.env[variable]