chore(deps): upgrade next.js to 16.2.4 (#4460)

* 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

* 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.

* 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.

* 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.

* revert(whitelabel): drop force-dynamic from landing layout

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.

* 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

* fix(navbar): preserve SSR HTML, drop Suspense bailout

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.

* 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).
This commit is contained in:
Waleed
2026-05-06 11:08:01 -07:00
committed by GitHub
parent ad88859d6e
commit 48331451b0
14 changed files with 58 additions and 35 deletions
+1 -1
View File
@@ -26,7 +26,7 @@
"fumadocs-openapi": "10.8.1",
"fumadocs-ui": "16.8.5",
"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",
+9 -1
View File
@@ -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 (
<Suspense fallback={null}>
<OAuthConsentInner />
</Suspense>
)
}
function OAuthConsentInner() {
const router = useRouter()
const searchParams = useSearchParams()
const { data: session } = useSession()
@@ -5,4 +5,6 @@ export const metadata: Metadata = {
title: 'Reset Password',
}
export const dynamic = 'force-dynamic'
export default ResetPasswordPage
@@ -4,7 +4,6 @@ import { useCallback, useContext, useEffect, useRef, useState, useSyncExternalSt
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'
@@ -52,12 +51,14 @@ interface 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(
+2
View File
@@ -3,6 +3,8 @@ import Link from 'next/link'
import { getEnv } from '@/lib/core/config/env'
import { ExternalRedirect, LegalLayout } from '@/app/(landing)/components'
export const dynamic = 'force-dynamic'
export const metadata: Metadata = {
title: 'Privacy Policy',
description:
+2
View File
@@ -3,6 +3,8 @@ import Link from 'next/link'
import { getEnv } from '@/lib/core/config/env'
import { ExternalRedirect, LegalLayout } from '@/app/(landing)/components'
export const dynamic = 'force-dynamic'
export const metadata: Metadata = {
title: 'Terms of Service',
description:
+2
View File
@@ -6,4 +6,6 @@ export const metadata: Metadata = {
robots: { index: false },
}
export const dynamic = 'force-dynamic'
export default Invite
+2
View File
@@ -1,6 +1,8 @@
import type { MetadataRoute } from 'next'
import { getBrandConfig } from '@/ee/whitelabeling'
export const dynamic = 'force-dynamic'
export default function manifest(): MetadataRoute.Manifest {
const brand = getBrandConfig()
+2
View File
@@ -6,4 +6,6 @@ export const metadata: Metadata = {
robots: { index: false },
}
export const dynamic = 'force-dynamic'
export default Unsubscribe
+8 -6
View File
@@ -1,14 +1,16 @@
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
* Reads NEXT_PUBLIC_* env vars in both client and server contexts.
* Client reads `window.__ENV` (populated by `<PublicEnvScript>`); 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) => 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({
+5 -5
View File
@@ -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 {
+3 -3
View File
@@ -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": {
+14 -14
View File
@@ -28,7 +28,7 @@
"fumadocs-openapi": "10.8.1",
"fumadocs-ui": "16.8.5",
"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",
@@ -213,7 +213,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",
@@ -482,9 +482,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",
@@ -1050,23 +1050,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=="],
@@ -3210,7 +3210,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=="],
+2 -2
View File
@@ -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"
},