From 3e961ec8137a76a8f500f0cca49f58f11e97af6b Mon Sep 17 00:00:00 2001 From: Lucas Doell Date: Fri, 26 Jun 2026 01:58:08 -0400 Subject: [PATCH 01/27] Enable Cache Components and stream the root provider tree Turn on the cacheComponents flag and restructure the root layout so the / shell prerenders while the request-dependent provider tree (locale, auth, feature flags) streams under Suspense. --- apps/web/next.config.ts | 1 + apps/web/src/app/layout.tsx | 107 ++++++++++++++++++++---------------- 2 files changed, 60 insertions(+), 48 deletions(-) diff --git a/apps/web/next.config.ts b/apps/web/next.config.ts index 17a0831a7..9104b5ba0 100644 --- a/apps/web/next.config.ts +++ b/apps/web/next.config.ts @@ -25,6 +25,7 @@ const cspHeader = ` `; const nextConfig: NextConfig = { + cacheComponents: true, reactCompiler: true, typedRoutes: true, transpilePackages: ["@parsertime/transactional"], diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx index ec83263d6..5d450a190 100644 --- a/apps/web/src/app/layout.tsx +++ b/apps/web/src/app/layout.tsx @@ -24,7 +24,7 @@ import { SpeedInsights } from "@vercel/speed-insights/next"; import { FlagValues } from "flags/react"; import type { Metadata } from "next"; import { NextIntlClientProvider } from "next-intl"; -import { Suspense } from "react"; +import { Suspense, type ReactNode } from "react"; import { getLocale, getMessages, getTranslations } from "next-intl/server"; import { Geist_Mono } from "next/font/google"; import localFont from "next/font/local"; @@ -81,7 +81,28 @@ const geistMono = Geist_Mono({ void register(); -export default async function RootLayout({ children }: LayoutProps<"/">) { +export default function RootLayout({ children }: LayoutProps<"/">) { + return ( + + + {/* The provider tree depends on request-time data (locale, auth, + feature flags), so it streams under Suspense while the document + shell prerenders. See migrating-to-cache-components. */} + + {children} + + + + ); +} + +async function RootProviders({ children }: { children: ReactNode }) { const locale = await getLocale(); const messages = await getMessages(); const session = await auth(); @@ -108,51 +129,41 @@ export default async function RootLayout({ children }: LayoutProps<"/">) { const flags = await resolveAllFlags(); return ( - - - - - - - - - - - - - - {children} - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + {children} + + + + + + + + + + + + + + + + + + ); } From 4330e0becda6a8c803bb84b5ccc933d7fa3ac426 Mon Sep 17 00:00:00 2001 From: Lucas Doell Date: Fri, 26 Jun 2026 02:02:31 -0400 Subject: [PATCH 02/27] Remove route segment configs and migrate unstable_cache to use cache cacheComponents rejects dynamic/runtime route segment configs, so drop the redundant runtime="nodejs"/"edge" and force-dynamic exports from API routes. Convert the five unstable_cache call sites to the use cache directive with cacheLife/cacheTag. --- apps/web/src/app/about/page.tsx | 39 +++++++------- .../map-calibration/[id]/align/apply/route.ts | 1 - .../[id]/align/revert/route.ts | 1 - .../admin/map-calibration/[id]/align/route.ts | 1 - .../map-calibration/upload/presign/route.ts | 2 - .../api/admin/map-calibration/upload/route.ts | 1 - .../src/app/api/cron/fsr-recompute/route.ts | 1 - .../app/api/cron/scrape-patch-notes/route.ts | 1 - .../src/app/api/cron/tempo-baseline/route.ts | 1 - apps/web/src/app/api/cron/tsr-enrich/route.ts | 1 - .../src/app/api/cron/tsr-recompute/route.ts | 1 - .../src/app/api/cron/usage-rollup/route.ts | 1 - apps/web/src/app/api/cron/wp-publish/route.ts | 1 - apps/web/src/app/api/cron/wp-retrain/route.ts | 1 - .../src/app/api/dashboard/overview/route.ts | 2 - apps/web/src/app/api/faceit/webhook/route.ts | 2 - apps/web/src/app/api/health/route.ts | 2 - .../src/app/api/image/[type]/[id]/route.ts | 2 - .../app/api/leaderboard/adjusted-csr/route.ts | 2 - apps/web/src/app/api/leaderboard/tsr/route.ts | 2 - apps/web/src/app/api/og/route.tsx | 2 - .../src/app/api/team/avatar-upload/route.ts | 2 - apps/web/src/app/api/usage/ingest/route.ts | 2 - .../src/app/api/user/avatar-upload/route.ts | 2 - .../src/app/api/user/banner-upload/route.ts | 2 - apps/web/src/components/home/landing-page.tsx | 37 +++++++------- apps/web/src/components/home/landing-stats.ts | 51 +++++++++---------- apps/web/src/lib/stat-percentiles.ts | 32 ++++++------ apps/web/src/lib/usage/queries.ts | 15 +++--- 29 files changed, 87 insertions(+), 123 deletions(-) diff --git a/apps/web/src/app/about/page.tsx b/apps/web/src/app/about/page.tsx index a1a72d438..e26b40624 100644 --- a/apps/web/src/app/about/page.tsx +++ b/apps/web/src/app/about/page.tsx @@ -13,7 +13,7 @@ import prisma from "@/lib/prisma"; import type { Metadata, Route } from "next"; import { getLocale, getTranslations } from "next-intl/server"; import { OrganizationJsonLd, ProfilePageJsonLd } from "next-seo"; -import { unstable_cache } from "next/cache"; +import { cacheLife } from "next/cache"; import { Instrument_Serif } from "next/font/google"; import type { SVGProps } from "react"; @@ -41,27 +41,26 @@ const instrumentSerif = Instrument_Serif({ variable: "--font-instrument-serif", }); -const getAboutPageStats = unstable_cache( - async () => { - const results = await Promise.allSettled([ - prisma.playerStat.count(), - prisma.calculatedStat.count(), - prisma.kill.count(), - prisma.map.count(), - ]); +async function getAboutPageStats() { + "use cache"; + cacheLife("hours"); - const [playerStatCount, calculatedStatCount, killCount, mapCount] = - results.map((r) => (r.status === "fulfilled" ? r.value : 0)); + const results = await Promise.allSettled([ + prisma.playerStat.count(), + prisma.calculatedStat.count(), + prisma.kill.count(), + prisma.map.count(), + ]); + + const [playerStatCount, calculatedStatCount, killCount, mapCount] = + results.map((r) => (r.status === "fulfilled" ? r.value : 0)); - return { - statsCount: Math.max(playerStatCount + calculatedStatCount, 800_000), - killCount: Math.max(killCount, 450_000), - mapCount: Math.max(mapCount, 6_000), - }; - }, - ["about-page-stats"], - { revalidate: 3600 } -); + return { + statsCount: Math.max(playerStatCount + calculatedStatCount, 800_000), + killCount: Math.max(killCount, 450_000), + mapCount: Math.max(mapCount, 6_000), + }; +} function roundCount(count: number): { value: number; suffix: string } { if (count >= 100_000) { diff --git a/apps/web/src/app/api/admin/map-calibration/[id]/align/apply/route.ts b/apps/web/src/app/api/admin/map-calibration/[id]/align/apply/route.ts index 5e90ac295..a66b07422 100644 --- a/apps/web/src/app/api/admin/map-calibration/[id]/align/apply/route.ts +++ b/apps/web/src/app/api/admin/map-calibration/[id]/align/apply/route.ts @@ -9,7 +9,6 @@ import { forbidden, unauthorized } from "next/navigation"; import { NextResponse } from "next/server"; import { z } from "zod"; -export const runtime = "nodejs"; export const maxDuration = 60; type Params = { params: Promise<{ id: string }> }; diff --git a/apps/web/src/app/api/admin/map-calibration/[id]/align/revert/route.ts b/apps/web/src/app/api/admin/map-calibration/[id]/align/revert/route.ts index b095f8802..d50d6a165 100644 --- a/apps/web/src/app/api/admin/map-calibration/[id]/align/revert/route.ts +++ b/apps/web/src/app/api/admin/map-calibration/[id]/align/revert/route.ts @@ -6,7 +6,6 @@ import { r2 } from "@/lib/r2"; import { forbidden, unauthorized } from "next/navigation"; import { NextResponse } from "next/server"; -export const runtime = "nodejs"; export const maxDuration = 60; type Params = { params: Promise<{ id: string }> }; diff --git a/apps/web/src/app/api/admin/map-calibration/[id]/align/route.ts b/apps/web/src/app/api/admin/map-calibration/[id]/align/route.ts index b56eeb902..aa31ec0a7 100644 --- a/apps/web/src/app/api/admin/map-calibration/[id]/align/route.ts +++ b/apps/web/src/app/api/admin/map-calibration/[id]/align/route.ts @@ -7,7 +7,6 @@ import { forbidden, unauthorized } from "next/navigation"; import { NextResponse } from "next/server"; import sharp from "sharp"; -export const runtime = "nodejs"; export const maxDuration = 60; type Params = { params: Promise<{ id: string }> }; diff --git a/apps/web/src/app/api/admin/map-calibration/upload/presign/route.ts b/apps/web/src/app/api/admin/map-calibration/upload/presign/route.ts index b633e4566..3f08b6aa5 100644 --- a/apps/web/src/app/api/admin/map-calibration/upload/presign/route.ts +++ b/apps/web/src/app/api/admin/map-calibration/upload/presign/route.ts @@ -5,8 +5,6 @@ import { r2 } from "@/lib/r2"; import { forbidden, unauthorized } from "next/navigation"; import { NextResponse } from "next/server"; -export const runtime = "nodejs"; - const ALLOWED_IMAGE_CONTENT_TYPES = new Set([ "image/jpeg", "image/png", diff --git a/apps/web/src/app/api/admin/map-calibration/upload/route.ts b/apps/web/src/app/api/admin/map-calibration/upload/route.ts index 62f291d1e..b321b47e4 100644 --- a/apps/web/src/app/api/admin/map-calibration/upload/route.ts +++ b/apps/web/src/app/api/admin/map-calibration/upload/route.ts @@ -6,7 +6,6 @@ import { forbidden, unauthorized } from "next/navigation"; import { NextResponse } from "next/server"; import sharp from "sharp"; -export const runtime = "nodejs"; export const maxDuration = 60; const MAX_UPLOAD_BYTES = 15 * 1024 * 1024; diff --git a/apps/web/src/app/api/cron/fsr-recompute/route.ts b/apps/web/src/app/api/cron/fsr-recompute/route.ts index 7b90eea54..85c8c35a8 100644 --- a/apps/web/src/app/api/cron/fsr-recompute/route.ts +++ b/apps/web/src/app/api/cron/fsr-recompute/route.ts @@ -2,7 +2,6 @@ import { Logger } from "@/lib/logger"; import { recomputeAllFsr } from "@/lib/fsr/compute"; import { timingSafeEqual } from "node:crypto"; -export const runtime = "nodejs"; export const maxDuration = 300; type AuthResult = diff --git a/apps/web/src/app/api/cron/scrape-patch-notes/route.ts b/apps/web/src/app/api/cron/scrape-patch-notes/route.ts index c294c4eb7..b1c557b72 100644 --- a/apps/web/src/app/api/cron/scrape-patch-notes/route.ts +++ b/apps/web/src/app/api/cron/scrape-patch-notes/route.ts @@ -3,7 +3,6 @@ import { Logger } from "@/lib/logger"; import { scrapeRecent } from "@/lib/overwatch/patch-scraper"; import { timingSafeEqual } from "node:crypto"; -export const runtime = "nodejs"; export const maxDuration = 60; type AuthResult = diff --git a/apps/web/src/app/api/cron/tempo-baseline/route.ts b/apps/web/src/app/api/cron/tempo-baseline/route.ts index 0cdfab4e7..2ce55de92 100644 --- a/apps/web/src/app/api/cron/tempo-baseline/route.ts +++ b/apps/web/src/app/api/cron/tempo-baseline/route.ts @@ -2,7 +2,6 @@ import { Logger } from "@/lib/logger"; import { recomputeTempoBaselines } from "@/lib/tempo/compute"; import { timingSafeEqual } from "node:crypto"; -export const runtime = "nodejs"; export const maxDuration = 300; type AuthResult = diff --git a/apps/web/src/app/api/cron/tsr-enrich/route.ts b/apps/web/src/app/api/cron/tsr-enrich/route.ts index e527bed5f..7089c4721 100644 --- a/apps/web/src/app/api/cron/tsr-enrich/route.ts +++ b/apps/web/src/app/api/cron/tsr-enrich/route.ts @@ -6,7 +6,6 @@ import { import prisma from "@/lib/prisma"; import { timingSafeEqual } from "node:crypto"; -export const runtime = "nodejs"; export const maxDuration = 300; // Number of stale players whose match history is re-ingested per run. The set diff --git a/apps/web/src/app/api/cron/tsr-recompute/route.ts b/apps/web/src/app/api/cron/tsr-recompute/route.ts index c267c9e7b..fa34a47cb 100644 --- a/apps/web/src/app/api/cron/tsr-recompute/route.ts +++ b/apps/web/src/app/api/cron/tsr-recompute/route.ts @@ -3,7 +3,6 @@ import { recomputeAllTeamTsrSnapshots } from "@/lib/matchmaker/snapshot"; import { recomputeAllTsrs } from "@/lib/tsr/replay"; import { timingSafeEqual } from "node:crypto"; -export const runtime = "nodejs"; export const maxDuration = 300; type AuthResult = diff --git a/apps/web/src/app/api/cron/usage-rollup/route.ts b/apps/web/src/app/api/cron/usage-rollup/route.ts index 7289705be..48fd080a8 100644 --- a/apps/web/src/app/api/cron/usage-rollup/route.ts +++ b/apps/web/src/app/api/cron/usage-rollup/route.ts @@ -8,7 +8,6 @@ import { } from "@/lib/usage/rollup"; import { timingSafeEqual } from "node:crypto"; -export const runtime = "nodejs"; export const maxDuration = 300; type AuthResult = diff --git a/apps/web/src/app/api/cron/wp-publish/route.ts b/apps/web/src/app/api/cron/wp-publish/route.ts index 0e2f8bd3d..4ee8bd7da 100644 --- a/apps/web/src/app/api/cron/wp-publish/route.ts +++ b/apps/web/src/app/api/cron/wp-publish/route.ts @@ -10,7 +10,6 @@ import { MODE_FAMILIES, type ModeFamily } from "@/lib/win-probability/types"; import { timingSafeEqual } from "node:crypto"; import { gunzipSync } from "node:zlib"; -export const runtime = "nodejs"; export const maxDuration = 60; type AuthResult = diff --git a/apps/web/src/app/api/cron/wp-retrain/route.ts b/apps/web/src/app/api/cron/wp-retrain/route.ts index f5f231003..c74809ba6 100644 --- a/apps/web/src/app/api/cron/wp-retrain/route.ts +++ b/apps/web/src/app/api/cron/wp-retrain/route.ts @@ -14,7 +14,6 @@ import { put } from "@vercel/blob"; import { waitUntil } from "@vercel/functions"; import { randomUUID, timingSafeEqual } from "node:crypto"; -export const runtime = "nodejs"; // The export (map fetch + buildRows over every map) is the long pole; training // now runs in a separate Python function, so this route only extracts, writes // the matrices to Blob, and fires the trainer. diff --git a/apps/web/src/app/api/dashboard/overview/route.ts b/apps/web/src/app/api/dashboard/overview/route.ts index 15d1dd557..118a7cd91 100644 --- a/apps/web/src/app/api/dashboard/overview/route.ts +++ b/apps/web/src/app/api/dashboard/overview/route.ts @@ -13,8 +13,6 @@ import { unauthorized } from "next/navigation"; import type { NextRequest } from "next/server"; import { NextResponse } from "next/server"; -export const runtime = "nodejs"; - function parseOptionalPositiveInt(value: string | null): number | undefined { if (value === null || !/^[1-9]\d*$/.test(value)) return undefined; const parsed = Number(value); diff --git a/apps/web/src/app/api/faceit/webhook/route.ts b/apps/web/src/app/api/faceit/webhook/route.ts index 1965f98fe..08ad5525c 100644 --- a/apps/web/src/app/api/faceit/webhook/route.ts +++ b/apps/web/src/app/api/faceit/webhook/route.ts @@ -4,8 +4,6 @@ import { recomputeAllTsrs } from "@/lib/tsr/replay"; import { kv } from "@vercel/kv"; import { timingSafeEqual } from "node:crypto"; -export const runtime = "nodejs"; - type FaceitWebhookEvent = { transaction_id?: string; event?: string; diff --git a/apps/web/src/app/api/health/route.ts b/apps/web/src/app/api/health/route.ts index c5b81326a..954b175c4 100644 --- a/apps/web/src/app/api/health/route.ts +++ b/apps/web/src/app/api/health/route.ts @@ -1,8 +1,6 @@ import prisma from "@/lib/prisma"; import { NextResponse } from "next/server"; -export const dynamic = "force-dynamic"; - export async function GET() { const checks: Record = {}; diff --git a/apps/web/src/app/api/image/[type]/[id]/route.ts b/apps/web/src/app/api/image/[type]/[id]/route.ts index 6ad81299e..f12b3ddec 100644 --- a/apps/web/src/app/api/image/[type]/[id]/route.ts +++ b/apps/web/src/app/api/image/[type]/[id]/route.ts @@ -2,8 +2,6 @@ import { imageKey, isImageKind } from "@/lib/avatar"; import { r2 } from "@/lib/r2"; import { NextResponse } from "next/server"; -export const runtime = "nodejs"; - const ID_RE = /^[A-Za-z0-9_-]+$/; export async function GET( diff --git a/apps/web/src/app/api/leaderboard/adjusted-csr/route.ts b/apps/web/src/app/api/leaderboard/adjusted-csr/route.ts index 04db81137..4067e6da3 100644 --- a/apps/web/src/app/api/leaderboard/adjusted-csr/route.ts +++ b/apps/web/src/app/api/leaderboard/adjusted-csr/route.ts @@ -5,8 +5,6 @@ import { import { Logger } from "@/lib/logger"; import { type HeroName, heroRoleMapping } from "@/types/heroes"; -export const runtime = "nodejs"; - function parseInt32(value: string | null, fallback: number): number { if (!value) return fallback; const n = Number.parseInt(value, 10); diff --git a/apps/web/src/app/api/leaderboard/tsr/route.ts b/apps/web/src/app/api/leaderboard/tsr/route.ts index 241826429..9e77c5a34 100644 --- a/apps/web/src/app/api/leaderboard/tsr/route.ts +++ b/apps/web/src/app/api/leaderboard/tsr/route.ts @@ -6,8 +6,6 @@ import { } from "@/lib/tsr/leaderboard"; import { FaceitTier, TsrRegion } from "@/generated/prisma/browser"; -export const runtime = "nodejs"; - const VALID_SORTS: TsrSortKey[] = ["rating", "matches", "recent"]; function parseEnum( diff --git a/apps/web/src/app/api/og/route.tsx b/apps/web/src/app/api/og/route.tsx index 9f1dd57bc..6c124e7d0 100644 --- a/apps/web/src/app/api/og/route.tsx +++ b/apps/web/src/app/api/og/route.tsx @@ -4,8 +4,6 @@ import { Logger } from "@/lib/logger"; import { ImageResponse } from "next/og"; -export const runtime = "edge"; - export async function GET(request: Request) { try { const { searchParams } = new URL(request.url); diff --git a/apps/web/src/app/api/team/avatar-upload/route.ts b/apps/web/src/app/api/team/avatar-upload/route.ts index 8b8bf6d2c..92f4d8b5d 100644 --- a/apps/web/src/app/api/team/avatar-upload/route.ts +++ b/apps/web/src/app/api/team/avatar-upload/route.ts @@ -10,8 +10,6 @@ import { kv } from "@vercel/kv"; import { unauthorized } from "next/navigation"; import { after, NextResponse } from "next/server"; -export const runtime = "nodejs"; - const MAX_BYTES = 5 * 1024 * 1024; export async function POST(request: Request): Promise { diff --git a/apps/web/src/app/api/usage/ingest/route.ts b/apps/web/src/app/api/usage/ingest/route.ts index 7e79bf3e7..6fd68aa85 100644 --- a/apps/web/src/app/api/usage/ingest/route.ts +++ b/apps/web/src/app/api/usage/ingest/route.ts @@ -11,8 +11,6 @@ import { kv } from "@vercel/kv"; import type { NextRequest } from "next/server"; import { z } from "zod"; -export const runtime = "nodejs"; - const ratelimit = new Ratelimit({ redis: kv, limiter: Ratelimit.slidingWindow(120, "1 m"), diff --git a/apps/web/src/app/api/user/avatar-upload/route.ts b/apps/web/src/app/api/user/avatar-upload/route.ts index 84caa2823..18d321714 100644 --- a/apps/web/src/app/api/user/avatar-upload/route.ts +++ b/apps/web/src/app/api/user/avatar-upload/route.ts @@ -10,8 +10,6 @@ import { kv } from "@vercel/kv"; import { unauthorized } from "next/navigation"; import { after, NextResponse } from "next/server"; -export const runtime = "nodejs"; - const MAX_BYTES = 5 * 1024 * 1024; export async function POST(request: Request): Promise { diff --git a/apps/web/src/app/api/user/banner-upload/route.ts b/apps/web/src/app/api/user/banner-upload/route.ts index d39649566..74cc70bd8 100644 --- a/apps/web/src/app/api/user/banner-upload/route.ts +++ b/apps/web/src/app/api/user/banner-upload/route.ts @@ -10,8 +10,6 @@ import { kv } from "@vercel/kv"; import { unauthorized } from "next/navigation"; import { after, NextResponse } from "next/server"; -export const runtime = "nodejs"; - const MAX_BYTES = 5 * 1024 * 1024; export async function POST(request: Request): Promise { diff --git a/apps/web/src/components/home/landing-page.tsx b/apps/web/src/components/home/landing-page.tsx index 8923d17bc..0d1116da2 100644 --- a/apps/web/src/components/home/landing-page.tsx +++ b/apps/web/src/components/home/landing-page.tsx @@ -18,7 +18,7 @@ import { import { get } from "@vercel/edge-config"; import type { Route } from "next"; import { getTranslations } from "next-intl/server"; -import { unstable_cache } from "next/cache"; +import { cacheLife } from "next/cache"; import Image from "next/image"; import Link from "next/link"; import type { SVGProps } from "react"; @@ -45,24 +45,23 @@ function formatRoundedCount(count: number): string { return `${count.toLocaleString("en-US")}+`; } -const getLandingPageStats = unstable_cache( - async () => { - const [playerStatCount, calculatedStatCount, killCount, mapCount] = - await Promise.all([ - prisma.playerStat.count(), - prisma.calculatedStat.count(), - prisma.kill.count(), - prisma.map.count(), - ]); - return { - statsCount: playerStatCount + calculatedStatCount, - killCount, - mapCount, - }; - }, - ["landing-page-stats"], - { revalidate: 3600 } -); +async function getLandingPageStats() { + "use cache"; + cacheLife("hours"); + + const [playerStatCount, calculatedStatCount, killCount, mapCount] = + await Promise.all([ + prisma.playerStat.count(), + prisma.calculatedStat.count(), + prisma.kill.count(), + prisma.map.count(), + ]); + return { + statsCount: playerStatCount + calculatedStatCount, + killCount, + mapCount, + }; +} type FooterNavigation = { social: { diff --git a/apps/web/src/components/home/landing-stats.ts b/apps/web/src/components/home/landing-stats.ts index 7eb76eb6f..012a2d1c6 100644 --- a/apps/web/src/components/home/landing-stats.ts +++ b/apps/web/src/components/home/landing-stats.ts @@ -1,6 +1,6 @@ import { getApproximateCounts } from "@/lib/approximate-count"; import prisma from "@/lib/prisma"; -import { unstable_cache } from "next/cache"; +import { cacheLife } from "next/cache"; /** * Rounds a count down to a "nice" number for display. @@ -21,30 +21,29 @@ export function roundCount(count: number): { value: number; suffix: string } { return { value: count, suffix: "+" }; } -export const getLandingPageStats = unstable_cache( - async () => { - // Large tables use approximate counts (pg_class.reltuples) to avoid a full - // sequential scan per cache refresh; Team is small, so an exact count is fine. - const [approx, teamResult] = await Promise.allSettled([ - getApproximateCounts(["PlayerStat", "CalculatedStat", "Kill", "Map"]), - prisma.team.count(), - ]); +export async function getLandingPageStats() { + "use cache"; + cacheLife("hours"); - const counts = - approx.status === "fulfilled" - ? approx.value - : { PlayerStat: 0, CalculatedStat: 0, Kill: 0, Map: 0 }; - const teamCount = teamResult.status === "fulfilled" ? teamResult.value : 0; + // Large tables use approximate counts (pg_class.reltuples) to avoid a full + // sequential scan per cache refresh; Team is small, so an exact count is fine. + const [approx, teamResult] = await Promise.allSettled([ + getApproximateCounts(["PlayerStat", "CalculatedStat", "Kill", "Map"]), + prisma.team.count(), + ]); - // Use known floor values if a query fails, so the landing page - // never shows "0+" for a stat that clearly isn't zero. - return { - statsCount: Math.max(counts.PlayerStat + counts.CalculatedStat, 800_000), - killCount: Math.max(counts.Kill, 450_000), - mapCount: Math.max(counts.Map, 6_000), - teamCount: Math.max(teamCount, 10), - }; - }, - ["landing-page-stats"], - { revalidate: 3600 } -); + const counts = + approx.status === "fulfilled" + ? approx.value + : { PlayerStat: 0, CalculatedStat: 0, Kill: 0, Map: 0 }; + const teamCount = teamResult.status === "fulfilled" ? teamResult.value : 0; + + // Use known floor values if a query fails, so the landing page + // never shows "0+" for a stat that clearly isn't zero. + return { + statsCount: Math.max(counts.PlayerStat + counts.CalculatedStat, 800_000), + killCount: Math.max(counts.Kill, 450_000), + mapCount: Math.max(counts.Map, 6_000), + teamCount: Math.max(teamCount, 10), + }; +} diff --git a/apps/web/src/lib/stat-percentiles.ts b/apps/web/src/lib/stat-percentiles.ts index f228a8017..65d6354e0 100644 --- a/apps/web/src/lib/stat-percentiles.ts +++ b/apps/web/src/lib/stat-percentiles.ts @@ -1,7 +1,7 @@ import prisma from "@/lib/prisma"; import type { HeroName } from "@/types/heroes"; import { Prisma } from "@/generated/prisma/client"; -import { unstable_cache } from "next/cache"; +import { cacheLife, cacheTag } from "next/cache"; // These baselines scan the whole PlayerStat table per hero (the app's biggest // rows-read offender) yet are global, slowly-changing population statistics — @@ -444,9 +444,16 @@ function buildMultiStatComparisonQuery({ `; } -async function compareMultipleStatsToDistributionUncached( +export async function compareMultipleStatsToDistribution( params: MultiStatComparisonParams ): Promise { + "use cache"; + cacheLife({ + revalidate: HERO_BASELINE_TTL_SECONDS, + expire: HERO_BASELINE_TTL_SECONDS * 4, + }); + cacheTag(HERO_BASELINE_TAG); + const query = buildMultiStatComparisonQuery(params); const result = await prisma.$queryRaw<{ hero: string; comparisons: StatComparison[] }[]>( @@ -463,12 +470,6 @@ async function compareMultipleStatsToDistributionUncached( }; } -export const compareMultipleStatsToDistribution = unstable_cache( - compareMultipleStatsToDistributionUncached, - ["multi-stat-comparison"], - { revalidate: HERO_BASELINE_TTL_SECONDS, tags: [HERO_BASELINE_TAG] } -); - function buildStatDistributionBaselineQuery({ hero, stat, @@ -522,20 +523,21 @@ function buildStatDistributionBaselineQuery({ `; } -async function getStatDistributionBaselineUncached( +export async function getStatDistributionBaseline( params: StatDistributionBaselineParams ): Promise { + "use cache"; + cacheLife({ + revalidate: HERO_BASELINE_TTL_SECONDS, + expire: HERO_BASELINE_TTL_SECONDS * 4, + }); + cacheTag(HERO_BASELINE_TAG); + const query = buildStatDistributionBaselineQuery(params); const result = await prisma.$queryRaw(query); return result[0] ?? null; } -export const getStatDistributionBaseline = unstable_cache( - getStatDistributionBaselineUncached, - ["stat-distribution-baseline"], - { revalidate: HERO_BASELINE_TTL_SECONDS, tags: [HERO_BASELINE_TAG] } -); - export type { StatDistributionBaseline, StatDistributionBaselineParams, diff --git a/apps/web/src/lib/usage/queries.ts b/apps/web/src/lib/usage/queries.ts index 9658c3a8f..deb1a933f 100644 --- a/apps/web/src/lib/usage/queries.ts +++ b/apps/web/src/lib/usage/queries.ts @@ -2,7 +2,7 @@ import "server-only"; import prisma from "@/lib/prisma"; import type { UsageEnv } from "@/generated/prisma/client"; -import { unstable_cache } from "next/cache"; +import { cacheLife } from "next/cache"; import { FUNNELS, computeFunnel, type FunnelStep } from "./funnels"; import { dayKey } from "./rollup"; @@ -170,10 +170,11 @@ async function loadFunnels( })); } -export function getFunnels(env: UsageEnv, days = 30): Promise { - return unstable_cache( - () => loadFunnels(env, days), - ["usage-funnels", env, String(days)], - { revalidate: 60 * 60 * 24 } // daily - )(); +export async function getFunnels( + env: UsageEnv, + days = 30 +): Promise { + "use cache"; + cacheLife("days"); + return loadFunnels(env, days); } From 34954df3be30713950b976e6035e4aad28aba9be Mon Sep 17 00:00:00 2001 From: Lucas Doell Date: Fri, 26 Jun 2026 02:05:53 -0400 Subject: [PATCH 03/27] Defer sidebar skeleton random width to after mount Math.random() during render is disallowed under Cache Components and broke static prerendering. Seed a deterministic width and randomize in useEffect, which also fixes the latent SSR/client hydration mismatch. --- apps/web/src/components/ui/sidebar.tsx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/apps/web/src/components/ui/sidebar.tsx b/apps/web/src/components/ui/sidebar.tsx index 208386482..a3f5f40af 100644 --- a/apps/web/src/components/ui/sidebar.tsx +++ b/apps/web/src/components/ui/sidebar.tsx @@ -591,10 +591,13 @@ function SidebarMenuSkeleton({ }: React.ComponentProps<"div"> & { showIcon?: boolean; }) { - // Random width between 50 to 90%. - const [width] = React.useState(() => { - return `${Math.floor(Math.random() * 40) + 50}%`; - }); + // Random width between 50 to 90%. Generated after mount so the prerender + // pass stays deterministic (Math.random() during render is disallowed under + // Cache Components) and the server/client markup matches on first paint. + const [width, setWidth] = React.useState("70%"); + React.useEffect(() => { + setWidth(`${Math.floor(Math.random() * 40) + 50}%`); + }, []); return (
Date: Fri, 26 Jun 2026 02:13:43 -0400 Subject: [PATCH 04/27] Mount the Vercel Toolbar on the client only The toolbar calls Math.random() during render, which broke the Cache Components static prerender for ~88 routes (surfacing under MetadataOutlet). Gate it behind a mount effect so it never participates in server prerender. --- .../devtools/client-vercel-toolbar.tsx | 19 +++++++++++++++++++ apps/web/src/components/devtools/index.tsx | 4 ++-- 2 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 apps/web/src/components/devtools/client-vercel-toolbar.tsx diff --git a/apps/web/src/components/devtools/client-vercel-toolbar.tsx b/apps/web/src/components/devtools/client-vercel-toolbar.tsx new file mode 100644 index 000000000..73b717cee --- /dev/null +++ b/apps/web/src/components/devtools/client-vercel-toolbar.tsx @@ -0,0 +1,19 @@ +"use client"; + +import { VercelToolbar } from "@vercel/toolbar/next"; +import { useEffect, useState } from "react"; + +/** + * The Vercel Toolbar calls `Math.random()` while rendering, which is disallowed + * during the Cache Components prerender pass. Mount it on the client only so it + * never participates in server prerendering. + */ +export function ClientVercelToolbar() { + const [mounted, setMounted] = useState(false); + + useEffect(() => { + setMounted(true); + }, []); + + return mounted ? : null; +} diff --git a/apps/web/src/components/devtools/index.tsx b/apps/web/src/components/devtools/index.tsx index bde73f74c..c943f426d 100644 --- a/apps/web/src/components/devtools/index.tsx +++ b/apps/web/src/components/devtools/index.tsx @@ -1,7 +1,7 @@ +import { ClientVercelToolbar } from "@/components/devtools/client-vercel-toolbar"; import { ReactScan } from "@/components/devtools/react-scan"; import { TailwindIndicator } from "@/components/devtools/tailwind-indicator"; import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; -import { VercelToolbar } from "@vercel/toolbar/next"; export function DevTools() { const isProd = process.env.NEXT_PUBLIC_VERCEL_ENV === "production"; @@ -12,7 +12,7 @@ export function DevTools() { - + ); } From b7bff57cee2ff00091a4da6ec3dbff5199e00254 Mon Sep 17 00:00:00 2001 From: Lucas Doell Date: Fri, 26 Jun 2026 02:26:34 -0400 Subject: [PATCH 05/27] Add a cookie-free metadata translator for Cache Components generateMetadata via next-intl's getTranslations always reads the locale cookie through the request config, forcing every route's to render dynamically and blocking the prerender. Resolve metadata in the default locale from the statically-imported catalog instead. Apply to the root layout and sign-in as the reference pattern. --- apps/web/src/app/(auth)/sign-in/page.tsx | 18 +++++------------- apps/web/src/app/layout.tsx | 13 ++++++++----- apps/web/src/lib/metadata-i18n.ts | 21 +++++++++++++++++++++ 3 files changed, 34 insertions(+), 18 deletions(-) create mode 100644 apps/web/src/lib/metadata-i18n.ts diff --git a/apps/web/src/app/(auth)/sign-in/page.tsx b/apps/web/src/app/(auth)/sign-in/page.tsx index c3ba4473a..b21ca7c2c 100644 --- a/apps/web/src/app/(auth)/sign-in/page.tsx +++ b/apps/web/src/app/(auth)/sign-in/page.tsx @@ -1,23 +1,15 @@ import { UserAuthForm } from "@/components/auth/user-auth-form"; import { Link } from "@/components/ui/link"; import { auth } from "@/lib/auth"; -import type { PagePropsWithLocale } from "@/types/next"; +import { defaultLocale } from "@/i18n/config"; +import { getMetadataTranslations } from "@/lib/metadata-i18n"; import type { Metadata, Route } from "next"; import { getTranslations } from "next-intl/server"; import Image from "next/image"; import { redirect } from "next/navigation"; -export async function generateMetadata( - props: PagePropsWithLocale<"/sign-in"> -): Promise { - const params = await props.params; - - const { locale } = params; - - const t = await getTranslations({ - locale, - namespace: "signInPage.metadataSignIn", - }); +export function generateMetadata(): Metadata { + const t = getMetadataTranslations("signInPage.metadataSignIn"); return { title: t("title"), @@ -35,7 +27,7 @@ export async function generateMetadata( height: 630, }, ], - locale, + locale: defaultLocale, }, }; } diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx index 5d450a190..f1305c141 100644 --- a/apps/web/src/app/layout.tsx +++ b/apps/web/src/app/layout.tsx @@ -13,6 +13,7 @@ import { AppRuntime } from "@/data/runtime"; import { UserService } from "@/data/user"; import { register } from "@/instrumentation"; import { auth } from "@/lib/auth"; +import { defaultLocale } from "@/i18n/config"; import { DSG_TEAM_ID } from "@/lib/brand-theme"; import { WebVitals } from "@/lib/axiom/client"; import { resolveAllFlags, toFlagValues } from "@/lib/flags-helpers"; @@ -25,15 +26,17 @@ import { FlagValues } from "flags/react"; import type { Metadata } from "next"; import { NextIntlClientProvider } from "next-intl"; import { Suspense, type ReactNode } from "react"; -import { getLocale, getMessages, getTranslations } from "next-intl/server"; +import { getLocale, getMessages } from "next-intl/server"; +import { getMetadataTranslations } from "@/lib/metadata-i18n"; import { Geist_Mono } from "next/font/google"; import localFont from "next/font/local"; import { NuqsAdapter } from "nuqs/adapters/next/app"; import "./globals.css"; -export async function generateMetadata(): Promise { - const locale = await getLocale(); - const t = await getTranslations({ locale, namespace: "metadata" }); +export function generateMetadata(): Metadata { + // Resolved in the default locale (see getMetadataTranslations) so the route's + // can be prerendered under Cache Components. + const t = getMetadataTranslations("metadata"); return { title: t("title"), @@ -52,7 +55,7 @@ export async function generateMetadata(): Promise { height: 630, }, ], - locale, + locale: defaultLocale, }, }; } diff --git a/apps/web/src/lib/metadata-i18n.ts b/apps/web/src/lib/metadata-i18n.ts new file mode 100644 index 000000000..5b71ea022 --- /dev/null +++ b/apps/web/src/lib/metadata-i18n.ts @@ -0,0 +1,21 @@ +import { defaultLocale } from "@/i18n/config"; +import { createTranslator } from "next-intl"; +import enMessages from "../../messages/en.json"; + +/** + * Cookie-free translator for `generateMetadata`. + * + * next-intl's `getTranslations` always resolves the per-request locale through + * the request config (`getUserLocale` → `cookies()`), which forces every + * route's `` to render dynamically under Cache Components. Metadata is + * instead resolved in the default locale from the statically-imported catalog + * so it can be prerendered. Page content is still localized at runtime via the + * `NextIntlClientProvider` in the root layout. + */ +export function getMetadataTranslations(namespace?: string) { + return createTranslator({ + locale: defaultLocale, + messages: enMessages, + namespace, + }); +} From 0e6f73f11d156538973672041a62c53dbcce162e Mon Sep 17 00:00:00 2001 From: Lucas Doell Date: Fri, 26 Jun 2026 02:38:00 -0400 Subject: [PATCH 06/27] Resolve route metadata in the default locale (Cache Components) Convert every generateMetadata to the cookie-free getMetadataTranslations helper so route rendering no longer reads the locale cookie and can prerender. Dynamic routes keep their param/auth/DB lookups; only the localized strings move to the default-locale catalog. Also defer the TSR leaderboard's relative-time labels to after mount. --- apps/web/src/app/(auth)/auth-error/layout.tsx | 4 +-- apps/web/src/app/(auth)/sign-up/page.tsx | 18 +++-------- .../src/app/(auth)/verify-request/layout.tsx | 4 +-- apps/web/src/app/[team]/compare/page.tsx | 12 ++----- apps/web/src/app/[team]/map-groups/page.tsx | 12 ++----- apps/web/src/app/[team]/ops/page.tsx | 11 ++----- .../app/[team]/scrim/[scrimId]/edit/page.tsx | 3 +- .../scrim/[scrimId]/map/[mapId]/page.tsx | 9 +++--- .../map/[mapId]/player/[playerId]/page.tsx | 9 +++--- .../src/app/[team]/scrim/[scrimId]/page.tsx | 9 +++--- apps/web/src/app/about/page.tsx | 8 ++--- apps/web/src/app/chat/layout.tsx | 4 +-- apps/web/src/app/coaching/layout.tsx | 4 +-- apps/web/src/app/contact/layout.tsx | 10 +++--- apps/web/src/app/dashboard/page.tsx | 18 +++-------- apps/web/src/app/data-labeling/layout.tsx | 4 +-- apps/web/src/app/debug/layout.tsx | 4 +-- apps/web/src/app/demo/page.tsx | 12 +++---- .../src/app/demo/player/[playerId]/page.tsx | 9 +++--- apps/web/src/app/faceit/page.tsx | 3 +- apps/web/src/app/faceit/player/page.tsx | 3 +- apps/web/src/app/leaderboard/csr/page.tsx | 3 +- apps/web/src/app/leaderboard/layout.tsx | 10 +++--- apps/web/src/app/leaderboard/page.tsx | 5 +-- apps/web/src/app/matchmaker/layout.tsx | 4 +-- apps/web/src/app/matchmaker/page.tsx | 4 +-- apps/web/src/app/notifications/layout.tsx | 6 ++-- apps/web/src/app/pricing/page.tsx | 11 +++---- apps/web/src/app/privacy/layout.tsx | 13 +++----- .../src/app/profile/[playerName]/layout.tsx | 8 ++--- apps/web/src/app/query/page.tsx | 5 +-- apps/web/src/app/ranked/page.tsx | 9 ++---- apps/web/src/app/reports/layout.tsx | 6 ++-- apps/web/src/app/scouting/layout.tsx | 12 +++---- apps/web/src/app/scouting/page.tsx | 5 +-- .../src/app/scouting/player/[slug]/layout.tsx | 8 ++--- apps/web/src/app/scouting/player/page.tsx | 5 +-- .../app/scouting/team/[teamAbbr]/layout.tsx | 8 ++--- apps/web/src/app/settings/layout.tsx | 11 +++---- apps/web/src/app/stats/[playerName]/page.tsx | 6 ++-- .../src/app/stats/hero/[heroName]/page.tsx | 10 +++--- apps/web/src/app/stats/hero/page.tsx | 12 +++---- apps/web/src/app/stats/layout.tsx | 12 +++---- .../src/app/stats/team/[teamId]/layout.tsx | 8 ++--- .../team/[teamId]/(app)/availability/page.tsx | 5 +-- .../(app)/availability/settings/page.tsx | 5 +-- apps/web/src/app/team/[teamId]/(app)/page.tsx | 6 ++-- .../app/team/[teamId]/(app)/targets/page.tsx | 5 +-- .../availability/[scheduleId]/page.tsx | 6 ++-- apps/web/src/app/team/join/layout.tsx | 6 ++-- apps/web/src/app/team/page.tsx | 12 +++---- apps/web/src/app/terms-of-service/layout.tsx | 13 +++----- apps/web/src/app/tournaments/create/page.tsx | 6 ++-- apps/web/src/app/tournaments/page.tsx | 6 ++-- .../leaderboard/tsr-leaderboard.tsx | 31 ++++++++++++++----- 55 files changed, 212 insertions(+), 240 deletions(-) diff --git a/apps/web/src/app/(auth)/auth-error/layout.tsx b/apps/web/src/app/(auth)/auth-error/layout.tsx index d96e75ceb..435a7f6bc 100644 --- a/apps/web/src/app/(auth)/auth-error/layout.tsx +++ b/apps/web/src/app/(auth)/auth-error/layout.tsx @@ -1,9 +1,9 @@ import { Footer } from "@/components/footer"; +import { getMetadataTranslations } from "@/lib/metadata-i18n"; import type { Metadata } from "next"; -import { getTranslations } from "next-intl/server"; export async function generateMetadata(): Promise { - const t = await getTranslations("authError.metadata"); + const t = getMetadataTranslations("authError.metadata"); return { title: t("title"), description: t("description") }; } diff --git a/apps/web/src/app/(auth)/sign-up/page.tsx b/apps/web/src/app/(auth)/sign-up/page.tsx index fc8794c25..8e38419ed 100644 --- a/apps/web/src/app/(auth)/sign-up/page.tsx +++ b/apps/web/src/app/(auth)/sign-up/page.tsx @@ -1,21 +1,13 @@ import { UserAuthForm } from "@/components/auth/user-auth-form"; import { Link } from "@/components/ui/link"; -import type { PagePropsWithLocale } from "@/types/next"; +import { defaultLocale } from "@/i18n/config"; +import { getMetadataTranslations } from "@/lib/metadata-i18n"; import type { Metadata } from "next"; import { getTranslations } from "next-intl/server"; import Image from "next/image"; -export async function generateMetadata( - props: PagePropsWithLocale<"/sign-up"> -): Promise { - const params = await props.params; - - const { locale } = params; - - const t = await getTranslations({ - locale, - namespace: "signInPage.metadataSignUp", - }); +export function generateMetadata(): Metadata { + const t = getMetadataTranslations("signInPage.metadataSignUp"); return { title: t("title"), @@ -33,7 +25,7 @@ export async function generateMetadata( height: 630, }, ], - locale, + locale: defaultLocale, }, }; } diff --git a/apps/web/src/app/(auth)/verify-request/layout.tsx b/apps/web/src/app/(auth)/verify-request/layout.tsx index e266d7fbf..8e4acf340 100644 --- a/apps/web/src/app/(auth)/verify-request/layout.tsx +++ b/apps/web/src/app/(auth)/verify-request/layout.tsx @@ -1,9 +1,9 @@ import { Footer } from "@/components/footer"; +import { getMetadataTranslations } from "@/lib/metadata-i18n"; import type { Metadata } from "next"; -import { getTranslations } from "next-intl/server"; export async function generateMetadata(): Promise { - const t = await getTranslations("verifyRequest.metadata"); + const t = getMetadataTranslations("verifyRequest.metadata"); return { title: t("title"), description: t("description") }; } diff --git a/apps/web/src/app/[team]/compare/page.tsx b/apps/web/src/app/[team]/compare/page.tsx index 9fe5102f3..6b48c8742 100644 --- a/apps/web/src/app/[team]/compare/page.tsx +++ b/apps/web/src/app/[team]/compare/page.tsx @@ -4,21 +4,15 @@ import { Effect } from "effect"; import { AppRuntime } from "@/data/runtime"; import { UserService } from "@/data/user"; import { auth } from "@/lib/auth"; +import { getMetadataTranslations } from "@/lib/metadata-i18n"; import prisma from "@/lib/prisma"; import type { PagePropsWithLocale } from "@/types/next"; import { $Enums } from "@/generated/prisma/browser"; import type { Metadata } from "next"; -import { getTranslations } from "next-intl/server"; import { notFound } from "next/navigation"; -export async function generateMetadata( - props: PagePropsWithLocale<"/[team]/compare"> -): Promise { - const params = await props.params; - const t = await getTranslations({ - locale: params.locale, - namespace: "comparePage.metadata", - }); +export function generateMetadata(): Metadata { + const t = getMetadataTranslations("comparePage.metadata"); return { title: t("title"), diff --git a/apps/web/src/app/[team]/map-groups/page.tsx b/apps/web/src/app/[team]/map-groups/page.tsx index a8505c1c2..f93a8e2b0 100644 --- a/apps/web/src/app/[team]/map-groups/page.tsx +++ b/apps/web/src/app/[team]/map-groups/page.tsx @@ -4,21 +4,15 @@ import { Effect } from "effect"; import { AppRuntime } from "@/data/runtime"; import { UserService } from "@/data/user"; import { auth } from "@/lib/auth"; +import { getMetadataTranslations } from "@/lib/metadata-i18n"; import prisma from "@/lib/prisma"; import type { PagePropsWithLocale } from "@/types/next"; import { $Enums } from "@/generated/prisma/browser"; import type { Metadata } from "next"; -import { getTranslations } from "next-intl/server"; import { notFound } from "next/navigation"; -export async function generateMetadata( - props: PagePropsWithLocale<"/[team]/map-groups"> -): Promise { - const params = await props.params; - const t = await getTranslations({ - locale: params.locale, - namespace: "mapGroupsPage.metadata", - }); +export function generateMetadata(): Metadata { + const t = getMetadataTranslations("mapGroupsPage.metadata"); return { title: t("title"), diff --git a/apps/web/src/app/[team]/ops/page.tsx b/apps/web/src/app/[team]/ops/page.tsx index a344d85be..88b7be6cf 100644 --- a/apps/web/src/app/[team]/ops/page.tsx +++ b/apps/web/src/app/[team]/ops/page.tsx @@ -5,6 +5,7 @@ import { AppRuntime } from "@/data/runtime"; import { UserService } from "@/data/user"; import { auth } from "@/lib/auth"; import { canManageTeam } from "@/lib/auth"; +import { getMetadataTranslations } from "@/lib/metadata-i18n"; import { listBlacklist, getBlacklistSuggestions, @@ -14,14 +15,8 @@ import type { Metadata } from "next"; import { getTranslations } from "next-intl/server"; import { notFound } from "next/navigation"; -export async function generateMetadata( - props: PagePropsWithLocale<"/[team]/ops"> -): Promise { - const params = await props.params; - const t = await getTranslations({ - locale: params.locale, - namespace: "teamOps", - }); +export function generateMetadata(): Metadata { + const t = getMetadataTranslations("teamOps"); return { title: t("title"), diff --git a/apps/web/src/app/[team]/scrim/[scrimId]/edit/page.tsx b/apps/web/src/app/[team]/scrim/[scrimId]/edit/page.tsx index 099b06bf1..202769a41 100644 --- a/apps/web/src/app/[team]/scrim/[scrimId]/edit/page.tsx +++ b/apps/web/src/app/[team]/scrim/[scrimId]/edit/page.tsx @@ -10,6 +10,7 @@ import { UserService } from "@/data/user"; import { auth } from "@/lib/auth"; import { scoutingTool } from "@/lib/flags"; import { resolveMapDataId } from "@/lib/map-data-resolver"; +import { getMetadataTranslations } from "@/lib/metadata-i18n"; import prisma from "@/lib/prisma"; import type { Metadata, Route } from "next"; import { getTranslations } from "next-intl/server"; @@ -18,7 +19,7 @@ export async function generateMetadata( props: PageProps<"/[team]/scrim/[scrimId]/edit"> ): Promise { const params = await props.params; - const t = await getTranslations("scrimPage.editMetadata"); + const t = getMetadataTranslations("scrimPage.editMetadata"); const scrim = await AppRuntime.runPromise( ScrimService.pipe( Effect.flatMap((svc) => svc.getScrim(parseInt(params.scrimId))) diff --git a/apps/web/src/app/[team]/scrim/[scrimId]/map/[mapId]/page.tsx b/apps/web/src/app/[team]/scrim/[scrimId]/map/[mapId]/page.tsx index 5739a9dd4..19d8828a1 100644 --- a/apps/web/src/app/[team]/scrim/[scrimId]/map/[mapId]/page.tsx +++ b/apps/web/src/app/[team]/scrim/[scrimId]/map/[mapId]/page.tsx @@ -11,9 +11,11 @@ import { MatchStoryService } from "@/data/map/match-story-service"; import { PlayerService } from "@/data/player"; import { AppRuntime } from "@/data/runtime"; import { UserService } from "@/data/user"; +import { defaultLocale } from "@/i18n/config"; import { auth, isAuthedToViewMap } from "@/lib/auth"; import { positionalData, tempoChart } from "@/lib/flags"; import { resolveScrimMapDataId } from "@/lib/map-data-resolver"; +import { getMetadataTranslations } from "@/lib/metadata-i18n"; import prisma from "@/lib/prisma"; import { getColorblindMode } from "@/lib/server-utils"; import { translateMapName } from "@/lib/utils"; @@ -34,10 +36,7 @@ export async function generateMetadata( Number.isSafeInteger(scrimId) && Number.isSafeInteger(mapId) && (await isAuthedToViewMap(scrimId, mapId)); - const t = await getTranslations({ - locale: params.locale, - namespace: "mapPage.mapMetadata", - }); + const t = getMetadataTranslations("mapPage.mapMetadata"); const mapName = canViewMap ? await prisma.matchStart.findFirst({ @@ -68,7 +67,7 @@ export async function generateMetadata( height: 630, }, ], - locale: params.locale, + locale: defaultLocale, }, }; } diff --git a/apps/web/src/app/[team]/scrim/[scrimId]/map/[mapId]/player/[playerId]/page.tsx b/apps/web/src/app/[team]/scrim/[scrimId]/map/[mapId]/player/[playerId]/page.tsx index e19a11f0d..5c283f2eb 100644 --- a/apps/web/src/app/[team]/scrim/[scrimId]/map/[mapId]/player/[playerId]/page.tsx +++ b/apps/web/src/app/[team]/scrim/[scrimId]/map/[mapId]/player/[playerId]/page.tsx @@ -11,8 +11,10 @@ import { PlayerService } from "@/data/player"; import { Effect } from "effect"; import { AppRuntime } from "@/data/runtime"; import { UserService } from "@/data/user"; +import { defaultLocale } from "@/i18n/config"; import { auth } from "@/lib/auth"; import { resolveMapDataId } from "@/lib/map-data-resolver"; +import { getMetadataTranslations } from "@/lib/metadata-i18n"; import prisma from "@/lib/prisma"; import { translateHeroName, translateMapName } from "@/lib/utils"; import { heroRoleMapping, type HeroName } from "@/types/heroes"; @@ -24,10 +26,7 @@ export async function generateMetadata( props: PagePropsWithLocale<"/[team]/scrim/[scrimId]/map/[mapId]/player/[playerId]"> ): Promise { const params = await props.params; - const t = await getTranslations({ - locale: params.locale, - namespace: "mapPage.playerMetadata", - }); + const t = getMetadataTranslations("mapPage.playerMetadata"); const playerName = decodeURIComponent(params.playerId); return { @@ -48,7 +47,7 @@ export async function generateMetadata( height: 630, }, ], - locale: params.locale, + locale: defaultLocale, }, }; } diff --git a/apps/web/src/app/[team]/scrim/[scrimId]/page.tsx b/apps/web/src/app/[team]/scrim/[scrimId]/page.tsx index 3e2c90cd1..727eb6198 100644 --- a/apps/web/src/app/[team]/scrim/[scrimId]/page.tsx +++ b/apps/web/src/app/[team]/scrim/[scrimId]/page.tsx @@ -32,8 +32,10 @@ import { resolveScrimMapWinners } from "@/data/scrim/map-winner-names"; import { Effect } from "effect"; import { AppRuntime } from "@/data/runtime"; import { UserService } from "@/data/user"; +import { defaultLocale } from "@/i18n/config"; import { auth, canManageTeam, isAuthedToViewScrim } from "@/lib/auth"; import { mapComparison, overviewCard, positionalData } from "@/lib/flags"; +import { getMetadataTranslations } from "@/lib/metadata-i18n"; import prisma from "@/lib/prisma"; import type { PagePropsWithLocale } from "@/types/next"; import { $Enums } from "@/generated/prisma/browser"; @@ -47,10 +49,7 @@ export async function generateMetadata( props: PagePropsWithLocale<"/[team]/scrim/[scrimId]"> ): Promise { const params = await props.params; - const t = await getTranslations({ - locale: params.locale, - namespace: "scrimPage.metadata", - }); + const t = getMetadataTranslations("scrimPage.metadata"); const scrimId = Number(params.scrimId); const canViewScrim = Number.isSafeInteger(scrimId) && @@ -86,7 +85,7 @@ export async function generateMetadata( height: 630, }, ], - locale: params.locale, + locale: defaultLocale, }, }; } diff --git a/apps/web/src/app/about/page.tsx b/apps/web/src/app/about/page.tsx index e26b40624..fd054d453 100644 --- a/apps/web/src/app/about/page.tsx +++ b/apps/web/src/app/about/page.tsx @@ -9,17 +9,17 @@ import { StatsCounter } from "@/components/home/new-landing/stats-counter"; import { TrackedLink } from "@/components/home/new-landing/tracked-link"; import { TrackedSection } from "@/components/home/new-landing/tracked-section"; import { auth } from "@/lib/auth"; +import { getMetadataTranslations } from "@/lib/metadata-i18n"; import prisma from "@/lib/prisma"; import type { Metadata, Route } from "next"; -import { getLocale, getTranslations } from "next-intl/server"; +import { getTranslations } from "next-intl/server"; import { OrganizationJsonLd, ProfilePageJsonLd } from "next-seo"; import { cacheLife } from "next/cache"; import { Instrument_Serif } from "next/font/google"; import type { SVGProps } from "react"; -export async function generateMetadata(): Promise { - const locale = await getLocale(); - const t = await getTranslations({ locale, namespace: "aboutPage.metadata" }); +export function generateMetadata(): Metadata { + const t = getMetadataTranslations("aboutPage.metadata"); return { title: t("title"), diff --git a/apps/web/src/app/chat/layout.tsx b/apps/web/src/app/chat/layout.tsx index 10fc940f8..6d19a6058 100644 --- a/apps/web/src/app/chat/layout.tsx +++ b/apps/web/src/app/chat/layout.tsx @@ -1,10 +1,10 @@ import { ChatSidebar } from "@/components/chat/chat-sidebar"; import { DashboardLayout } from "@/components/dashboard-layout"; +import { getMetadataTranslations } from "@/lib/metadata-i18n"; import type { Metadata } from "next"; -import { getTranslations } from "next-intl/server"; export async function generateMetadata(): Promise { - const t = await getTranslations("analyst.metadata"); + const t = getMetadataTranslations("analyst.metadata"); return { title: t("title"), description: t("description"), diff --git a/apps/web/src/app/coaching/layout.tsx b/apps/web/src/app/coaching/layout.tsx index ad57798f5..f9b00523c 100644 --- a/apps/web/src/app/coaching/layout.tsx +++ b/apps/web/src/app/coaching/layout.tsx @@ -1,9 +1,9 @@ import { DashboardLayout } from "@/components/dashboard-layout"; +import { getMetadataTranslations } from "@/lib/metadata-i18n"; import type { Metadata } from "next"; -import { getTranslations } from "next-intl/server"; export async function generateMetadata(): Promise { - const t = await getTranslations("coaching.metadata"); + const t = getMetadataTranslations("coaching.metadata"); return { title: t("title"), diff --git a/apps/web/src/app/contact/layout.tsx b/apps/web/src/app/contact/layout.tsx index 2c91831de..8709ba7f7 100644 --- a/apps/web/src/app/contact/layout.tsx +++ b/apps/web/src/app/contact/layout.tsx @@ -1,11 +1,11 @@ import { Footer } from "@/components/footer"; import { Header } from "@/components/header"; +import { defaultLocale } from "@/i18n/config"; +import { getMetadataTranslations } from "@/lib/metadata-i18n"; import type { Metadata } from "next"; -import { getLocale, getTranslations } from "next-intl/server"; -export async function generateMetadata(): Promise { - const t = await getTranslations("contactPage.metadata"); - const locale = await getLocale(); +export function generateMetadata(): Metadata { + const t = getMetadataTranslations("contactPage.metadata"); return { title: t("title"), @@ -23,7 +23,7 @@ export async function generateMetadata(): Promise { height: 630, }, ], - locale, + locale: defaultLocale, }, }; } diff --git a/apps/web/src/app/dashboard/page.tsx b/apps/web/src/app/dashboard/page.tsx index b4e15ffe1..97038fa60 100644 --- a/apps/web/src/app/dashboard/page.tsx +++ b/apps/web/src/app/dashboard/page.tsx @@ -5,24 +5,16 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Effect } from "effect"; import { AppRuntime } from "@/data/runtime"; import { UserService } from "@/data/user"; +import { defaultLocale } from "@/i18n/config"; import { auth } from "@/lib/auth"; +import { getMetadataTranslations } from "@/lib/metadata-i18n"; import { getPendingFeedbackCount } from "@/lib/team-ops/scrim-feedback"; -import type { PagePropsWithLocale } from "@/types/next"; import { $Enums } from "@/generated/prisma/browser"; import type { Metadata } from "next"; import { getTranslations } from "next-intl/server"; -export async function generateMetadata( - props: PagePropsWithLocale<"/dashboard"> -): Promise { - const params = await props.params; - - const { locale } = params; - - const t = await getTranslations({ - locale, - namespace: "dashboard.metadata", - }); +export function generateMetadata(): Metadata { + const t = getMetadataTranslations("dashboard.metadata"); return { title: t("title"), @@ -40,7 +32,7 @@ export async function generateMetadata( height: 630, }, ], - locale, + locale: defaultLocale, }, }; } diff --git a/apps/web/src/app/data-labeling/layout.tsx b/apps/web/src/app/data-labeling/layout.tsx index 96520670f..20e360d26 100644 --- a/apps/web/src/app/data-labeling/layout.tsx +++ b/apps/web/src/app/data-labeling/layout.tsx @@ -1,9 +1,9 @@ import { DashboardLayout } from "@/components/dashboard-layout"; +import { getMetadataTranslations } from "@/lib/metadata-i18n"; import type { Metadata } from "next"; -import { getTranslations } from "next-intl/server"; export async function generateMetadata(): Promise { - const t = await getTranslations("dataLabeling.metadata"); + const t = getMetadataTranslations("dataLabeling.metadata"); return { title: t("title"), diff --git a/apps/web/src/app/debug/layout.tsx b/apps/web/src/app/debug/layout.tsx index cce99fbf5..ceb9ac52f 100644 --- a/apps/web/src/app/debug/layout.tsx +++ b/apps/web/src/app/debug/layout.tsx @@ -1,9 +1,9 @@ import { DashboardLayout } from "@/components/dashboard-layout"; +import { getMetadataTranslations } from "@/lib/metadata-i18n"; import type { Metadata } from "next"; -import { getTranslations } from "next-intl/server"; export async function generateMetadata(): Promise { - const t = await getTranslations("debugPage.metadata"); + const t = getMetadataTranslations("debugPage.metadata"); return { title: t("title"), description: t("description") }; } diff --git a/apps/web/src/app/demo/page.tsx b/apps/web/src/app/demo/page.tsx index 30c333050..cfc9c9254 100644 --- a/apps/web/src/app/demo/page.tsx +++ b/apps/web/src/app/demo/page.tsx @@ -16,21 +16,19 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Effect } from "effect"; import { AppRuntime } from "@/data/runtime"; import { PlayerService } from "@/data/player"; +import { defaultLocale } from "@/i18n/config"; import { resolveMapDataId } from "@/lib/map-data-resolver"; +import { getMetadataTranslations } from "@/lib/metadata-i18n"; import prisma from "@/lib/prisma"; import { toTitleCase, translateMapName } from "@/lib/utils"; -import type { PagePropsWithLocale } from "@/types/next"; import type { Metadata } from "next"; import { getTranslations } from "next-intl/server"; import Link from "next/link"; const DEMO_MAP_ID = 10148; -export async function generateMetadata( - props: PagePropsWithLocale<"/demo"> -): Promise { - const params = await props.params; - const t = await getTranslations("demoPage.metadata"); +export async function generateMetadata(): Promise { + const t = getMetadataTranslations("demoPage.metadata"); const mapDataId = await resolveMapDataId(DEMO_MAP_ID); const mapName = await prisma.matchStart.findFirst({ @@ -64,7 +62,7 @@ export async function generateMetadata( height: 630, }, ], - locale: params.locale, + locale: defaultLocale, }, }; } diff --git a/apps/web/src/app/demo/player/[playerId]/page.tsx b/apps/web/src/app/demo/player/[playerId]/page.tsx index f63e86f7d..2ff37a59b 100644 --- a/apps/web/src/app/demo/player/[playerId]/page.tsx +++ b/apps/web/src/app/demo/player/[playerId]/page.tsx @@ -11,7 +11,9 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Effect } from "effect"; import { AppRuntime } from "@/data/runtime"; import { PlayerService } from "@/data/player"; +import { defaultLocale } from "@/i18n/config"; import { resolveMapDataId } from "@/lib/map-data-resolver"; +import { getMetadataTranslations } from "@/lib/metadata-i18n"; import prisma from "@/lib/prisma"; import { toTitleCase } from "@/lib/utils"; import type { PagePropsWithLocale } from "@/types/next"; @@ -32,10 +34,7 @@ export async function generateMetadata( props: PagePropsWithLocale<"/demo/player/[playerId]"> ): Promise { const params = await props.params; - const t = await getTranslations({ - locale: params.locale, - namespace: "mapPage.playerMetadata", - }); + const t = getMetadataTranslations("mapPage.playerMetadata"); const playerName = decodePlayerId(params.playerId) ?? "Player"; return { @@ -56,7 +55,7 @@ export async function generateMetadata( height: 630, }, ], - locale: params.locale, + locale: defaultLocale, }, }; } diff --git a/apps/web/src/app/faceit/page.tsx b/apps/web/src/app/faceit/page.tsx index aa9922fcf..3bb4356b5 100644 --- a/apps/web/src/app/faceit/page.tsx +++ b/apps/web/src/app/faceit/page.tsx @@ -3,12 +3,13 @@ import { AppRuntime } from "@/data/runtime"; import { FaceitTeamScoutingService } from "@/data/faceit"; import { Effect } from "effect"; import { faceitScouting } from "@/lib/flags"; +import { getMetadataTranslations } from "@/lib/metadata-i18n"; import type { Metadata } from "next"; import { getTranslations } from "next-intl/server"; import { notFound } from "next/navigation"; export async function generateMetadata(): Promise { - const t = await getTranslations("faceitScoutingPage"); + const t = getMetadataTranslations("faceitScoutingPage"); return { title: t("metadata.searchTitle"), description: t("subtitle") }; } diff --git a/apps/web/src/app/faceit/player/page.tsx b/apps/web/src/app/faceit/player/page.tsx index 39041d3fd..6c8920e25 100644 --- a/apps/web/src/app/faceit/player/page.tsx +++ b/apps/web/src/app/faceit/player/page.tsx @@ -3,12 +3,13 @@ import { AppRuntime } from "@/data/runtime"; import { FaceitPlayerScoutingService } from "@/data/faceit"; import { Effect } from "effect"; import { faceitScouting } from "@/lib/flags"; +import { getMetadataTranslations } from "@/lib/metadata-i18n"; import type { Metadata } from "next"; import { getTranslations } from "next-intl/server"; import { notFound } from "next/navigation"; export async function generateMetadata(): Promise { - const t = await getTranslations("faceitPlayerPage"); + const t = getMetadataTranslations("faceitPlayerPage"); return { title: t("metadata.searchTitle"), description: t("subtitle") }; } diff --git a/apps/web/src/app/leaderboard/csr/page.tsx b/apps/web/src/app/leaderboard/csr/page.tsx index 5b89dd7ac..0494a0cdc 100644 --- a/apps/web/src/app/leaderboard/csr/page.tsx +++ b/apps/web/src/app/leaderboard/csr/page.tsx @@ -8,13 +8,14 @@ import { AccordionTrigger, } from "@/components/ui/accordion"; import { getCompositeSRLeaderboard } from "@/lib/hero-rating"; +import { getMetadataTranslations } from "@/lib/metadata-i18n"; import { getHeroNames, toHero } from "@/lib/utils"; import { heroRoleMapping, type HeroName } from "@/types/heroes"; import type { Metadata } from "next"; import { getTranslations } from "next-intl/server"; export async function generateMetadata(): Promise { - const t = await getTranslations("leaderboardPage.csrPage.metadata"); + const t = getMetadataTranslations("leaderboardPage.csrPage.metadata"); return { title: t("title"), description: t("description"), diff --git a/apps/web/src/app/leaderboard/layout.tsx b/apps/web/src/app/leaderboard/layout.tsx index b5c90695b..03966a69f 100644 --- a/apps/web/src/app/leaderboard/layout.tsx +++ b/apps/web/src/app/leaderboard/layout.tsx @@ -1,10 +1,10 @@ import { DashboardLayout } from "@/components/dashboard-layout"; +import { defaultLocale } from "@/i18n/config"; +import { getMetadataTranslations } from "@/lib/metadata-i18n"; import type { Metadata } from "next"; -import { getLocale, getTranslations } from "next-intl/server"; -export async function generateMetadata(): Promise { - const t = await getTranslations("leaderboardPage.metadata"); - const locale = await getLocale(); +export function generateMetadata(): Metadata { + const t = getMetadataTranslations("leaderboardPage.metadata"); return { title: t("title"), @@ -22,7 +22,7 @@ export async function generateMetadata(): Promise { height: 630, }, ], - locale, + locale: defaultLocale, }, }; } diff --git a/apps/web/src/app/leaderboard/page.tsx b/apps/web/src/app/leaderboard/page.tsx index 0105aed7c..5ed7e3a8f 100644 --- a/apps/web/src/app/leaderboard/page.tsx +++ b/apps/web/src/app/leaderboard/page.tsx @@ -2,12 +2,13 @@ import { LeaderboardHub, type MetricStats, } from "@/components/leaderboard/leaderboard-hub"; +import { getMetadataTranslations } from "@/lib/metadata-i18n"; import { getInitialTsrLeaderboard } from "@/lib/tsr/leaderboard"; import type { Metadata } from "next"; import { getFormatter, getTranslations } from "next-intl/server"; -export async function generateMetadata(): Promise { - const t = await getTranslations("leaderboardPage.hub.metadata"); +export function generateMetadata(): Metadata { + const t = getMetadataTranslations("leaderboardPage.hub.metadata"); return { title: t("title"), description: t("description"), diff --git a/apps/web/src/app/matchmaker/layout.tsx b/apps/web/src/app/matchmaker/layout.tsx index 9fdc6cabd..0990ed095 100644 --- a/apps/web/src/app/matchmaker/layout.tsx +++ b/apps/web/src/app/matchmaker/layout.tsx @@ -1,9 +1,9 @@ import { DashboardLayout } from "@/components/dashboard-layout"; +import { getMetadataTranslations } from "@/lib/metadata-i18n"; import type { Metadata } from "next"; -import { getTranslations } from "next-intl/server"; export async function generateMetadata(): Promise { - const t = await getTranslations("matchmaker.metadata"); + const t = getMetadataTranslations("matchmaker.metadata"); return { title: t("title"), diff --git a/apps/web/src/app/matchmaker/page.tsx b/apps/web/src/app/matchmaker/page.tsx index 1dc5b0a68..217e12237 100644 --- a/apps/web/src/app/matchmaker/page.tsx +++ b/apps/web/src/app/matchmaker/page.tsx @@ -5,12 +5,12 @@ import { MatchmakerHub, type HubTeam, } from "@/components/matchmaker/matchmaker-hub"; +import { getMetadataTranslations } from "@/lib/metadata-i18n"; import { getTierBucket } from "@/lib/tsr/tier-bucket"; import type { Metadata } from "next"; -import { getTranslations } from "next-intl/server"; export async function generateMetadata(): Promise { - const t = await getTranslations("matchmaker.metadata"); + const t = getMetadataTranslations("matchmaker.metadata"); return { title: t("title"), description: t("description"), diff --git a/apps/web/src/app/notifications/layout.tsx b/apps/web/src/app/notifications/layout.tsx index 54ad8a2b2..0c491f821 100644 --- a/apps/web/src/app/notifications/layout.tsx +++ b/apps/web/src/app/notifications/layout.tsx @@ -1,9 +1,9 @@ import { DashboardLayout } from "@/components/dashboard-layout"; +import { getMetadataTranslations } from "@/lib/metadata-i18n"; import type { Metadata } from "next"; -import { getTranslations } from "next-intl/server"; -export async function generateMetadata(): Promise { - const t = await getTranslations("notifications.metadata"); +export function generateMetadata(): Metadata { + const t = getMetadataTranslations("notifications.metadata"); return { title: t("title"), description: t("description") }; } diff --git a/apps/web/src/app/pricing/page.tsx b/apps/web/src/app/pricing/page.tsx index f7589c2ad..3e586db38 100644 --- a/apps/web/src/app/pricing/page.tsx +++ b/apps/web/src/app/pricing/page.tsx @@ -15,18 +15,15 @@ import { Effect } from "effect"; import { AppRuntime } from "@/data/runtime"; import { UserService } from "@/data/user"; import { auth } from "@/lib/auth"; +import { getMetadataTranslations } from "@/lib/metadata-i18n"; import { toTitleCase } from "@/lib/utils"; import type { Metadata, Route } from "next"; -import { getLocale, getTranslations } from "next-intl/server"; +import { getTranslations } from "next-intl/server"; import { Instrument_Serif } from "next/font/google"; import type { SVGProps } from "react"; -export async function generateMetadata(): Promise { - const locale = await getLocale(); - const t = await getTranslations({ - locale, - namespace: "pricingPage.metadata", - }); +export function generateMetadata(): Metadata { + const t = getMetadataTranslations("pricingPage.metadata"); return { title: t("title"), diff --git a/apps/web/src/app/privacy/layout.tsx b/apps/web/src/app/privacy/layout.tsx index fe8f25e67..38bf44ed0 100644 --- a/apps/web/src/app/privacy/layout.tsx +++ b/apps/web/src/app/privacy/layout.tsx @@ -1,14 +1,11 @@ import { Footer } from "@/components/footer"; import { Header } from "@/components/header"; +import { defaultLocale } from "@/i18n/config"; +import { getMetadataTranslations } from "@/lib/metadata-i18n"; import type { Metadata } from "next"; -import { getLocale, getTranslations } from "next-intl/server"; -export async function generateMetadata(): Promise { - const locale = await getLocale(); - const t = await getTranslations({ - locale, - namespace: "privacyPage.metadata", - }); +export function generateMetadata(): Metadata { + const t = getMetadataTranslations("privacyPage.metadata"); return { title: t("title"), @@ -27,7 +24,7 @@ export async function generateMetadata(): Promise { height: 630, }, ], - locale, + locale: defaultLocale, }, }; } diff --git a/apps/web/src/app/profile/[playerName]/layout.tsx b/apps/web/src/app/profile/[playerName]/layout.tsx index bf7b9d06d..556434742 100644 --- a/apps/web/src/app/profile/[playerName]/layout.tsx +++ b/apps/web/src/app/profile/[playerName]/layout.tsx @@ -1,13 +1,13 @@ import { DashboardLayout } from "@/components/dashboard-layout"; +import { defaultLocale } from "@/i18n/config"; +import { getMetadataTranslations } from "@/lib/metadata-i18n"; import type { Metadata } from "next"; -import { getLocale, getTranslations } from "next-intl/server"; export async function generateMetadata( props: LayoutProps<"/profile/[playerName]"> ): Promise { const params = await props.params; - const t = await getTranslations("profilePage.layoutMetadata"); - const locale = await getLocale(); + const t = getMetadataTranslations("profilePage.layoutMetadata"); const playerName = decodeURIComponent(params.playerName); return { @@ -26,7 +26,7 @@ export async function generateMetadata( height: 630, }, ], - locale, + locale: defaultLocale, }, }; } diff --git a/apps/web/src/app/query/page.tsx b/apps/web/src/app/query/page.tsx index 9cc1d6e0b..3e03dcfba 100644 --- a/apps/web/src/app/query/page.tsx +++ b/apps/web/src/app/query/page.tsx @@ -2,13 +2,14 @@ import { DashboardLayout } from "@/components/dashboard-layout"; import { QueryBuilder } from "@/components/query-builder/query-builder"; import { auth } from "@/lib/auth"; import { queryBuilder } from "@/lib/flags"; +import { getMetadataTranslations } from "@/lib/metadata-i18n"; import { getViewableTeams, listSavedQueries } from "@/lib/query-builder/server"; import type { Metadata } from "next"; import { getTranslations } from "next-intl/server"; import { notFound, redirect } from "next/navigation"; -export async function generateMetadata(): Promise { - const t = await getTranslations("queryBuilderPage.metadata"); +export function generateMetadata(): Metadata { + const t = getMetadataTranslations("queryBuilderPage.metadata"); return { title: t("title"), description: t("description") }; } diff --git a/apps/web/src/app/ranked/page.tsx b/apps/web/src/app/ranked/page.tsx index fd563169e..c1631c7fb 100644 --- a/apps/web/src/app/ranked/page.tsx +++ b/apps/web/src/app/ranked/page.tsx @@ -15,18 +15,15 @@ import { RankedService } from "@/data/ranked"; import { AppRuntime } from "@/data/runtime"; import { UserService } from "@/data/user"; import { auth } from "@/lib/auth"; -import type { PagePropsWithLocale } from "@/types/next"; +import { getMetadataTranslations } from "@/lib/metadata-i18n"; import { Effect } from "effect"; import { Crosshair, Plus } from "lucide-react"; import type { Metadata } from "next"; import { getTranslations } from "next-intl/server"; import { redirect } from "next/navigation"; -export async function generateMetadata( - props: PagePropsWithLocale<"/ranked"> -): Promise { - const { locale } = await props.params; - const t = await getTranslations({ locale, namespace: "ranked.metadata" }); +export function generateMetadata(): Metadata { + const t = getMetadataTranslations("ranked.metadata"); return { title: t("title"), description: t("description") }; } diff --git a/apps/web/src/app/reports/layout.tsx b/apps/web/src/app/reports/layout.tsx index dd7b06b3a..767ab710e 100644 --- a/apps/web/src/app/reports/layout.tsx +++ b/apps/web/src/app/reports/layout.tsx @@ -1,9 +1,9 @@ import { DashboardLayout } from "@/components/dashboard-layout"; +import { getMetadataTranslations } from "@/lib/metadata-i18n"; import type { Metadata } from "next"; -import { getTranslations } from "next-intl/server"; -export async function generateMetadata(): Promise { - const t = await getTranslations("reportsPage.metadata"); +export function generateMetadata(): Metadata { + const t = getMetadataTranslations("reportsPage.metadata"); return { title: t("title"), diff --git a/apps/web/src/app/scouting/layout.tsx b/apps/web/src/app/scouting/layout.tsx index b4029c0df..bc5b09911 100644 --- a/apps/web/src/app/scouting/layout.tsx +++ b/apps/web/src/app/scouting/layout.tsx @@ -1,12 +1,10 @@ import { DashboardLayout } from "@/components/dashboard-layout"; +import { defaultLocale } from "@/i18n/config"; +import { getMetadataTranslations } from "@/lib/metadata-i18n"; import type { Metadata } from "next"; -import { getTranslations } from "next-intl/server"; -export async function generateMetadata( - props: LayoutProps<"/scouting"> -): Promise { - const params = (await props.params) as { locale: string }; - const t = await getTranslations("scoutingPage.metadata"); +export function generateMetadata(): Metadata { + const t = getMetadataTranslations("scoutingPage.metadata"); return { title: t("title"), @@ -24,7 +22,7 @@ export async function generateMetadata( height: 630, }, ], - locale: params.locale, + locale: defaultLocale, }, }; } diff --git a/apps/web/src/app/scouting/page.tsx b/apps/web/src/app/scouting/page.tsx index 44ba637cb..30f7f1045 100644 --- a/apps/web/src/app/scouting/page.tsx +++ b/apps/web/src/app/scouting/page.tsx @@ -3,12 +3,13 @@ import { AppRuntime } from "@/data/runtime"; import { ScoutingService } from "@/data/scouting"; import { Effect } from "effect"; import { scoutingTool } from "@/lib/flags"; +import { getMetadataTranslations } from "@/lib/metadata-i18n"; import type { Metadata } from "next"; import { getTranslations } from "next-intl/server"; import { notFound } from "next/navigation"; -export async function generateMetadata(): Promise { - const t = await getTranslations("scoutingPage.metadata"); +export function generateMetadata(): Metadata { + const t = getMetadataTranslations("scoutingPage.metadata"); return { title: t("title"), description: t("description"), diff --git a/apps/web/src/app/scouting/player/[slug]/layout.tsx b/apps/web/src/app/scouting/player/[slug]/layout.tsx index 0eac47411..27d6b234c 100644 --- a/apps/web/src/app/scouting/player/[slug]/layout.tsx +++ b/apps/web/src/app/scouting/player/[slug]/layout.tsx @@ -1,12 +1,12 @@ +import { defaultLocale } from "@/i18n/config"; +import { getMetadataTranslations } from "@/lib/metadata-i18n"; import type { Metadata } from "next"; -import { getLocale, getTranslations } from "next-intl/server"; export async function generateMetadata( props: LayoutProps<"/scouting/player/[slug]"> ): Promise { const params = await props.params; - const t = await getTranslations("scoutingPage.player.profile.metadata"); - const locale = await getLocale(); + const t = getMetadataTranslations("scoutingPage.player.profile.metadata"); const player = decodeURIComponent(params.slug); @@ -26,7 +26,7 @@ export async function generateMetadata( height: 630, }, ], - locale, + locale: defaultLocale, }, }; } diff --git a/apps/web/src/app/scouting/player/page.tsx b/apps/web/src/app/scouting/player/page.tsx index 17e714790..d44a18fbe 100644 --- a/apps/web/src/app/scouting/player/page.tsx +++ b/apps/web/src/app/scouting/player/page.tsx @@ -3,12 +3,13 @@ import { Effect } from "effect"; import { AppRuntime } from "@/data/runtime"; import { ScoutingService } from "@/data/player"; import { scoutingTool } from "@/lib/flags"; +import { getMetadataTranslations } from "@/lib/metadata-i18n"; import type { Metadata } from "next"; import { getTranslations } from "next-intl/server"; import { notFound } from "next/navigation"; -export async function generateMetadata(): Promise { - const t = await getTranslations("scoutingPage.player.metadata"); +export function generateMetadata(): Metadata { + const t = getMetadataTranslations("scoutingPage.player.metadata"); return { title: t("title"), description: t("description"), diff --git a/apps/web/src/app/scouting/team/[teamAbbr]/layout.tsx b/apps/web/src/app/scouting/team/[teamAbbr]/layout.tsx index 647fc47dc..1095beafe 100644 --- a/apps/web/src/app/scouting/team/[teamAbbr]/layout.tsx +++ b/apps/web/src/app/scouting/team/[teamAbbr]/layout.tsx @@ -1,12 +1,12 @@ +import { defaultLocale } from "@/i18n/config"; +import { getMetadataTranslations } from "@/lib/metadata-i18n"; import type { Metadata } from "next"; -import { getLocale, getTranslations } from "next-intl/server"; export async function generateMetadata( props: LayoutProps<"/scouting/team/[teamAbbr]"> ): Promise { const params = await props.params; - const t = await getTranslations("scoutingPage.team.metadata"); - const locale = await getLocale(); + const t = getMetadataTranslations("scoutingPage.team.metadata"); const team = decodeURIComponent(params.teamAbbr); @@ -26,7 +26,7 @@ export async function generateMetadata( height: 630, }, ], - locale, + locale: defaultLocale, }, }; } diff --git a/apps/web/src/app/settings/layout.tsx b/apps/web/src/app/settings/layout.tsx index 6f30bbd27..08c3a2e90 100644 --- a/apps/web/src/app/settings/layout.tsx +++ b/apps/web/src/app/settings/layout.tsx @@ -4,16 +4,15 @@ import { Separator } from "@/components/ui/separator"; import { Effect } from "effect"; import { AppRuntime } from "@/data/runtime"; import { UserService } from "@/data/user"; +import { defaultLocale } from "@/i18n/config"; import { auth } from "@/lib/auth"; +import { getMetadataTranslations } from "@/lib/metadata-i18n"; import { $Enums } from "@/generated/prisma/browser"; import type { Metadata, Route } from "next"; import { getTranslations } from "next-intl/server"; -export async function generateMetadata( - props: LayoutProps<"/settings"> -): Promise { - const params = (await props.params) as { locale: string }; - const t = await getTranslations("settingsPage.metadata"); +export function generateMetadata(): Metadata { + const t = getMetadataTranslations("settingsPage.metadata"); return { title: t("title"), @@ -31,7 +30,7 @@ export async function generateMetadata( height: 630, }, ], - locale: params.locale, + locale: defaultLocale, }, }; } diff --git a/apps/web/src/app/stats/[playerName]/page.tsx b/apps/web/src/app/stats/[playerName]/page.tsx index acd3eb24e..d655a5420 100644 --- a/apps/web/src/app/stats/[playerName]/page.tsx +++ b/apps/web/src/app/stats/[playerName]/page.tsx @@ -11,6 +11,8 @@ import { UserService } from "@/data/user"; import { auth, getViewableScrimIds } from "@/lib/auth"; import { Permission } from "@/lib/permissions"; import prisma from "@/lib/prisma"; +import { defaultLocale } from "@/i18n/config"; +import { getMetadataTranslations } from "@/lib/metadata-i18n"; import type { PagePropsWithLocale } from "@/types/next"; import type { Kill, PlayerStat, Scrim } from "@/generated/prisma/client"; import type { Metadata } from "next"; @@ -21,7 +23,7 @@ export async function generateMetadata( props: PagePropsWithLocale<"/stats/[playerName]"> ): Promise { const params = await props.params; - const t = await getTranslations("statsPage.playerMetadata"); + const t = getMetadataTranslations("statsPage.playerMetadata"); const playerName = decodeURIComponent(params.playerName); const suffix = playerName.endsWith("s") ? "'" : "'s"; @@ -44,7 +46,7 @@ export async function generateMetadata( height: 630, }, ], - locale: params.locale, + locale: defaultLocale, }, }; } diff --git a/apps/web/src/app/stats/hero/[heroName]/page.tsx b/apps/web/src/app/stats/hero/[heroName]/page.tsx index d99621bc0..34112c8d1 100644 --- a/apps/web/src/app/stats/hero/[heroName]/page.tsx +++ b/apps/web/src/app/stats/hero/[heroName]/page.tsx @@ -9,9 +9,11 @@ import { Effect } from "effect"; import { AppRuntime } from "@/data/runtime"; import { UserService } from "@/data/user"; import { auth, getViewableScrimIds } from "@/lib/auth"; +import { defaultLocale } from "@/i18n/config"; +import { getMetadataTranslations } from "@/lib/metadata-i18n"; import { Permission } from "@/lib/permissions"; import prisma from "@/lib/prisma"; -import { translateHeroName } from "@/lib/utils"; +import { toHero, translateHeroName } from "@/lib/utils"; import { type HeroName, heroRoleMapping } from "@/types/heroes"; import type { PagePropsWithLocale } from "@/types/next"; import type { Kill, PlayerStat, Scrim } from "@/generated/prisma/client"; @@ -24,8 +26,8 @@ export async function generateMetadata( ): Promise { const params = await props.params; const heroName = decodeURIComponent(params.heroName); - const hero = await translateHeroName(heroName); - const t = await getTranslations("statsPage.heroMetadata"); + const hero = getMetadataTranslations("heroes")(toHero(heroName)); + const t = getMetadataTranslations("statsPage.heroMetadata"); return { title: t("title", { hero }), @@ -43,7 +45,7 @@ export async function generateMetadata( height: 630, }, ], - locale: params.locale, + locale: defaultLocale, }, }; } diff --git a/apps/web/src/app/stats/hero/page.tsx b/apps/web/src/app/stats/hero/page.tsx index c24bab518..1cdcf5581 100644 --- a/apps/web/src/app/stats/hero/page.tsx +++ b/apps/web/src/app/stats/hero/page.tsx @@ -1,17 +1,15 @@ import { StatPanel } from "@/components/player/stat-panel"; import { Link } from "@/components/ui/link"; +import { defaultLocale } from "@/i18n/config"; +import { getMetadataTranslations } from "@/lib/metadata-i18n"; import { getHeroNames, toHero } from "@/lib/utils"; import { type HeroName, roleHeroMapping } from "@/types/heroes"; -import type { PagePropsWithLocale } from "@/types/next"; import type { Metadata, Route } from "next"; import { getTranslations } from "next-intl/server"; import Image from "next/image"; -export async function generateMetadata( - props: PagePropsWithLocale<"/stats/hero"> -): Promise { - const params = await props.params; - const t = await getTranslations("statsPage.heroStatsMetadata"); +export function generateMetadata(): Metadata { + const t = getMetadataTranslations("statsPage.heroStatsMetadata"); return { title: t("title"), description: t("description"), @@ -28,7 +26,7 @@ export async function generateMetadata( height: 630, }, ], - locale: params.locale, + locale: defaultLocale, }, }; } diff --git a/apps/web/src/app/stats/layout.tsx b/apps/web/src/app/stats/layout.tsx index 024a3513b..d98e8ff70 100644 --- a/apps/web/src/app/stats/layout.tsx +++ b/apps/web/src/app/stats/layout.tsx @@ -1,12 +1,10 @@ import { DashboardLayout } from "@/components/dashboard-layout"; +import { defaultLocale } from "@/i18n/config"; +import { getMetadataTranslations } from "@/lib/metadata-i18n"; import type { Metadata } from "next"; -import { getTranslations } from "next-intl/server"; -export async function generateMetadata( - props: LayoutProps<"/stats"> -): Promise { - const params = (await props.params) as { locale: string }; - const t = await getTranslations("statsPage.layoutMetadata"); +export function generateMetadata(): Metadata { + const t = getMetadataTranslations("statsPage.layoutMetadata"); return { title: t("title"), @@ -24,7 +22,7 @@ export async function generateMetadata( height: 630, }, ], - locale: params.locale, + locale: defaultLocale, }, }; } diff --git a/apps/web/src/app/stats/team/[teamId]/layout.tsx b/apps/web/src/app/stats/team/[teamId]/layout.tsx index ddf357b8f..6b422f1b3 100644 --- a/apps/web/src/app/stats/team/[teamId]/layout.tsx +++ b/apps/web/src/app/stats/team/[teamId]/layout.tsx @@ -2,18 +2,18 @@ import { RangeTransitionProvider } from "@/components/stats/team/range-transitio import { TeamStatsContent } from "@/components/stats/team/team-stats-content"; import { TeamStatsHeaderClient } from "@/components/stats/team/team-stats-header-client"; import { TeamStatsTabsNav } from "@/components/stats/team/team-stats-tabs-nav"; +import { defaultLocale } from "@/i18n/config"; import { isAuthedToViewTeam } from "@/lib/auth"; import { positionalData, simulationTool } from "@/lib/flags"; +import { getMetadataTranslations } from "@/lib/metadata-i18n"; import prisma from "@/lib/prisma"; import type { Metadata } from "next"; -import { getLocale, getTranslations } from "next-intl/server"; export async function generateMetadata( props: LayoutProps<"/stats/team/[teamId]"> ): Promise { const params = await props.params; - const t = await getTranslations("teamStatsPage.layoutMetadata"); - const locale = await getLocale(); + const t = getMetadataTranslations("teamStatsPage.layoutMetadata"); const teamId = parseInt(params.teamId); const canViewTeam = @@ -45,7 +45,7 @@ export async function generateMetadata( height: 630, }, ], - locale, + locale: defaultLocale, }, }; } diff --git a/apps/web/src/app/team/[teamId]/(app)/availability/page.tsx b/apps/web/src/app/team/[teamId]/(app)/availability/page.tsx index 0bd7726cc..cfefdb308 100644 --- a/apps/web/src/app/team/[teamId]/(app)/availability/page.tsx +++ b/apps/web/src/app/team/[teamId]/(app)/availability/page.tsx @@ -2,6 +2,7 @@ import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { isTeamOwnerOrManager } from "@/lib/auth"; import { weekEndInTz, weekStartInTz } from "@/lib/availability/tz"; +import { getMetadataTranslations } from "@/lib/metadata-i18n"; import prisma from "@/lib/prisma"; import { revalidatePath } from "next/cache"; import Link from "next/link"; @@ -11,8 +12,8 @@ import type { Metadata } from "next"; type PageProps = { params: Promise<{ teamId: string }> }; -export async function generateMetadata(): Promise { - const t = await getTranslations("availability.metadata"); +export function generateMetadata(): Metadata { + const t = getMetadataTranslations("availability.metadata"); return { title: t("title"), description: t("description") }; } diff --git a/apps/web/src/app/team/[teamId]/(app)/availability/settings/page.tsx b/apps/web/src/app/team/[teamId]/(app)/availability/settings/page.tsx index bde11d063..c3aecb0c9 100644 --- a/apps/web/src/app/team/[teamId]/(app)/availability/settings/page.tsx +++ b/apps/web/src/app/team/[teamId]/(app)/availability/settings/page.tsx @@ -1,5 +1,6 @@ import { AvailabilitySettingsForm } from "@/components/availability/availability-settings-form"; import { isTeamOwnerOrManager } from "@/lib/auth"; +import { getMetadataTranslations } from "@/lib/metadata-i18n"; import prisma from "@/lib/prisma"; import { notFound, redirect } from "next/navigation"; import { getTranslations } from "next-intl/server"; @@ -7,8 +8,8 @@ import type { Metadata } from "next"; type PageProps = { params: Promise<{ teamId: string }> }; -export async function generateMetadata(): Promise { - const t = await getTranslations("availability.settingsPage.metadata"); +export function generateMetadata(): Metadata { + const t = getMetadataTranslations("availability.settingsPage.metadata"); return { title: t("title"), description: t("description") }; } diff --git a/apps/web/src/app/team/[teamId]/(app)/page.tsx b/apps/web/src/app/team/[teamId]/(app)/page.tsx index 871d61dce..cc11ab41a 100644 --- a/apps/web/src/app/team/[teamId]/(app)/page.tsx +++ b/apps/web/src/app/team/[teamId]/(app)/page.tsx @@ -15,8 +15,10 @@ import { Effect } from "effect"; import { AppRuntime } from "@/data/runtime"; import { ScoutingService } from "@/data/scouting"; import { UserService } from "@/data/user"; +import { defaultLocale } from "@/i18n/config"; import { auth, isAuthedToViewTeam } from "@/lib/auth"; import { scoutingTool } from "@/lib/flags"; +import { getMetadataTranslations } from "@/lib/metadata-i18n"; import prisma from "@/lib/prisma"; import { computeTeamTsr } from "@/lib/tsr/team"; import type { PagePropsWithLocale } from "@/types/next"; @@ -29,7 +31,7 @@ export async function generateMetadata( props: PagePropsWithLocale<"/team/[teamId]"> ): Promise { const params = await props.params; - const t = await getTranslations("teamPage.teamMetadata"); + const t = getMetadataTranslations("teamPage.teamMetadata"); const teamId = Number(params.teamId); const canViewTeam = Number.isSafeInteger(teamId) && @@ -61,7 +63,7 @@ export async function generateMetadata( height: 630, }, ], - locale: params.locale, + locale: defaultLocale, }, }; } diff --git a/apps/web/src/app/team/[teamId]/(app)/targets/page.tsx b/apps/web/src/app/team/[teamId]/(app)/targets/page.tsx index 741682a45..f4c8eff83 100644 --- a/apps/web/src/app/team/[teamId]/(app)/targets/page.tsx +++ b/apps/web/src/app/team/[teamId]/(app)/targets/page.tsx @@ -10,6 +10,7 @@ import { Effect } from "effect"; import { AppRuntime } from "@/data/runtime"; import { UserService } from "@/data/user"; import { auth } from "@/lib/auth"; +import { getMetadataTranslations } from "@/lib/metadata-i18n"; import prisma from "@/lib/prisma"; import type { RoleName } from "@/lib/target-stats"; import { type HeroName, heroRoleMapping } from "@/types/heroes"; @@ -21,8 +22,8 @@ type Props = { params: Promise<{ teamId: string }>; }; -export async function generateMetadata(): Promise { - const t = await getTranslations("targets.metadata"); +export function generateMetadata(): Metadata { + const t = getMetadataTranslations("targets.metadata"); return { title: t("title"), description: t("description") }; } diff --git a/apps/web/src/app/team/[teamId]/availability/[scheduleId]/page.tsx b/apps/web/src/app/team/[teamId]/availability/[scheduleId]/page.tsx index 36ac9f896..76d9c31c2 100644 --- a/apps/web/src/app/team/[teamId]/availability/[scheduleId]/page.tsx +++ b/apps/web/src/app/team/[teamId]/availability/[scheduleId]/page.tsx @@ -1,16 +1,16 @@ import { AvailabilityFillView } from "@/components/availability/availability-fill-view"; import { auth } from "@/lib/auth"; +import { getMetadataTranslations } from "@/lib/metadata-i18n"; import prisma from "@/lib/prisma"; import type { Metadata } from "next"; -import { getTranslations } from "next-intl/server"; import { notFound } from "next/navigation"; type PageProps = { params: Promise<{ teamId: string; scheduleId: string }>; }; -export async function generateMetadata(): Promise { - const t = await getTranslations("availability.fillView.metadata"); +export function generateMetadata(): Metadata { + const t = getMetadataTranslations("availability.fillView.metadata"); return { title: t("title"), description: t("description") }; } diff --git a/apps/web/src/app/team/join/layout.tsx b/apps/web/src/app/team/join/layout.tsx index a025b8e3c..5dfb125b4 100644 --- a/apps/web/src/app/team/join/layout.tsx +++ b/apps/web/src/app/team/join/layout.tsx @@ -1,9 +1,9 @@ import { DashboardLayout } from "@/components/dashboard-layout"; +import { getMetadataTranslations } from "@/lib/metadata-i18n"; import type { Metadata } from "next"; -import { getTranslations } from "next-intl/server"; -export async function generateMetadata(): Promise { - const t = await getTranslations("teamPage.joinMetadata"); +export function generateMetadata(): Metadata { + const t = getMetadataTranslations("teamPage.joinMetadata"); return { title: t("title"), description: t("description") }; } diff --git a/apps/web/src/app/team/page.tsx b/apps/web/src/app/team/page.tsx index 0d2da155e..15b178044 100644 --- a/apps/web/src/app/team/page.tsx +++ b/apps/web/src/app/team/page.tsx @@ -6,19 +6,17 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Effect } from "effect"; import { AppRuntime } from "@/data/runtime"; import { UserService } from "@/data/user"; +import { defaultLocale } from "@/i18n/config"; import { auth } from "@/lib/auth"; +import { getMetadataTranslations } from "@/lib/metadata-i18n"; import prisma from "@/lib/prisma"; -import type { PagePropsWithLocale } from "@/types/next"; import { $Enums } from "@/generated/prisma/browser"; import type { Metadata } from "next"; import { getTranslations } from "next-intl/server"; import { redirect } from "next/navigation"; -export async function generateMetadata( - props: PagePropsWithLocale<"/team"> -): Promise { - const params = await props.params; - const t = await getTranslations("teamPage.metadata"); +export function generateMetadata(): Metadata { + const t = getMetadataTranslations("teamPage.metadata"); return { title: t("title"), description: t("description"), @@ -35,7 +33,7 @@ export async function generateMetadata( height: 630, }, ], - locale: params.locale, + locale: defaultLocale, }, }; } diff --git a/apps/web/src/app/terms-of-service/layout.tsx b/apps/web/src/app/terms-of-service/layout.tsx index 2fa29cbd9..bd1c14463 100644 --- a/apps/web/src/app/terms-of-service/layout.tsx +++ b/apps/web/src/app/terms-of-service/layout.tsx @@ -1,14 +1,11 @@ import { Footer } from "@/components/footer"; import { Header } from "@/components/header"; +import { defaultLocale } from "@/i18n/config"; +import { getMetadataTranslations } from "@/lib/metadata-i18n"; import type { Metadata } from "next"; -import { getLocale, getTranslations } from "next-intl/server"; -export async function generateMetadata(): Promise { - const locale = await getLocale(); - const t = await getTranslations({ - locale, - namespace: "termsPage.metadata", - }); +export function generateMetadata(): Metadata { + const t = getMetadataTranslations("termsPage.metadata"); return { title: t("title"), @@ -27,7 +24,7 @@ export async function generateMetadata(): Promise { height: 630, }, ], - locale, + locale: defaultLocale, }, }; } diff --git a/apps/web/src/app/tournaments/create/page.tsx b/apps/web/src/app/tournaments/create/page.tsx index 3c364d986..4bd9994f9 100644 --- a/apps/web/src/app/tournaments/create/page.tsx +++ b/apps/web/src/app/tournaments/create/page.tsx @@ -1,12 +1,12 @@ import { DashboardLayout } from "@/components/dashboard-layout"; import { CreateTournamentButton } from "@/components/tournament/create-tournament-button"; import { tournament } from "@/lib/flags"; +import { getMetadataTranslations } from "@/lib/metadata-i18n"; import type { Metadata } from "next"; -import { getTranslations } from "next-intl/server"; import { notFound } from "next/navigation"; -export async function generateMetadata(): Promise { - const t = await getTranslations("tournamentsPage.create.metadata"); +export function generateMetadata(): Metadata { + const t = getMetadataTranslations("tournamentsPage.create.metadata"); return { title: t("title"), description: t("description") }; } diff --git a/apps/web/src/app/tournaments/page.tsx b/apps/web/src/app/tournaments/page.tsx index 9b79fe66d..5786ab1c4 100644 --- a/apps/web/src/app/tournaments/page.tsx +++ b/apps/web/src/app/tournaments/page.tsx @@ -6,12 +6,12 @@ import { AppRuntime } from "@/data/runtime"; import { TournamentService } from "@/data/tournament"; import { auth } from "@/lib/auth"; import { tournament } from "@/lib/flags"; +import { getMetadataTranslations } from "@/lib/metadata-i18n"; import type { Metadata } from "next"; -import { getTranslations } from "next-intl/server"; import { notFound, redirect } from "next/navigation"; -export async function generateMetadata(): Promise { - const t = await getTranslations("tournamentsPage.metadata"); +export function generateMetadata(): Metadata { + const t = getMetadataTranslations("tournamentsPage.metadata"); return { title: t("title"), description: t("description") }; } diff --git a/apps/web/src/components/leaderboard/tsr-leaderboard.tsx b/apps/web/src/components/leaderboard/tsr-leaderboard.tsx index 160899bae..0e878dca0 100644 --- a/apps/web/src/components/leaderboard/tsr-leaderboard.tsx +++ b/apps/web/src/components/leaderboard/tsr-leaderboard.tsx @@ -61,9 +61,16 @@ const COLS = const PAGE_SIZE = 50; -function formatRelativeDate(d: Date | null, t: TsrMessages): string { - if (!d) return "—"; - const days = Math.floor((Date.now() - d.getTime()) / 86_400_000); +// `now` is resolved after mount (see the `now` state below) so the prerender +// pass stays deterministic — reading the current time during render is +// disallowed under Cache Components. +function formatRelativeDate( + d: Date | null, + t: TsrMessages, + now: number | null +): string { + if (!d || now === null) return "—"; + const days = Math.floor((now - d.getTime()) / 86_400_000); if (days < 1) return t("relative.today"); if (days < 7) return t("relative.days", { count: days }); if (days < 30) return t("relative.weeks", { count: Math.floor(days / 7) }); @@ -71,9 +78,13 @@ function formatRelativeDate(d: Date | null, t: TsrMessages): string { return t("relative.years", { count: Math.floor(days / 365) }); } -function formatComputedAt(d: Date | null, t: TsrMessages): string | null { - if (!d) return null; - const minutes = Math.floor((Date.now() - d.getTime()) / 60_000); +function formatComputedAt( + d: Date | null, + t: TsrMessages, + now: number | null +): string | null { + if (!d || now === null) return null; + const minutes = Math.floor((now - d.getTime()) / 60_000); if (minutes < 1) return t("relative.justNow"); if (minutes < 60) return t("relative.minutesAgo", { count: minutes }); const hours = Math.floor(minutes / 60); @@ -180,6 +191,10 @@ async function fetchSnapshot( export function TsrLeaderboard({ initialSnapshot }: Props) { const t = useTranslations("leaderboardPage.tsr"); const formatter = useFormatter(); + // Resolved after mount so relative-time labels don't read the clock during + // the prerender pass (disallowed under Cache Components). + const [now, setNow] = useState(null); + useEffect(() => setNow(Date.now()), []); const [region, setRegion] = useState("All"); const [tier, setTier] = useState("All"); const [sortKey, setSortKey] = useState("rating"); @@ -237,7 +252,7 @@ export function TsrLeaderboard({ initialSnapshot }: Props) { } }, [result.data, offset]); - const computedAt = formatComputedAt(snapshot.meta.computedAt, t); + const computedAt = formatComputedAt(snapshot.meta.computedAt, t, now); const showInactive = debouncedQuery.trim() !== ""; const [selectedId, setSelectedId] = useState(null); @@ -595,7 +610,7 @@ function PlayerRow({
- {formatRelativeDate(row.lastMatchAt, t)} + {formatRelativeDate(row.lastMatchAt, t, now)}
); From 88e004054f2ad23b777a161a1877b213252d339d Mon Sep 17 00:00:00 2001 From: Lucas Doell Date: Fri, 26 Jun 2026 02:52:17 -0400 Subject: [PATCH 07/27] Skip OpenTelemetry setup during the build phase MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The OTel SDK generates span/trace IDs with Math.random(), which Cache Components forbids during prerendering — every prerendered route that ran a traced Prisma/Effect query failed. Telemetry has no value at build time, so skip tracer registration in phase-production-build; runtime requests still trace and export normally. --- apps/web/src/instrumentation.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/web/src/instrumentation.ts b/apps/web/src/instrumentation.ts index 258741260..1ca430c22 100644 --- a/apps/web/src/instrumentation.ts +++ b/apps/web/src/instrumentation.ts @@ -7,6 +7,12 @@ type RequestErrorArgs = Parameters; export async function register() { if (process.env.NEXT_RUNTIME !== "nodejs") return; + // OpenTelemetry generates span/trace IDs with Math.random(), which Cache + // Components disallows during prerendering. Build-time prerendering needs no + // telemetry, so skip tracer setup in the build phase to keep prerenders + // deterministic. Runtime requests still register and export traces. + if (process.env.NEXT_PHASE === "phase-production-build") return; + const { registerNode } = await import("./instrumentation-node"); registerNode(); } From fff2bce442df00547eae5d2b9720f4ae7652a6a9 Mon Sep 17 00:00:00 2001 From: Lucas Doell Date: Fri, 26 Jun 2026 02:53:37 -0400 Subject: [PATCH 08/27] Cache the map hero trends page data The recent-trends aggregation uses a Date.now() time window; wrap it in use cache (cacheLife hours) so it prerenders and the timestamp resolves once at cache fill. --- apps/web/src/app/stats/map/page.tsx | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/apps/web/src/app/stats/map/page.tsx b/apps/web/src/app/stats/map/page.tsx index 1a20cfe7a..8a2c8a7af 100644 --- a/apps/web/src/app/stats/map/page.tsx +++ b/apps/web/src/app/stats/map/page.tsx @@ -4,13 +4,20 @@ import { getOverwatchPatches } from "@/data/overwatch/patches-service"; import { AppRuntime } from "@/data/runtime"; import { Effect } from "effect"; import type { Metadata } from "next"; +import { cacheLife } from "next/cache"; export const metadata: Metadata = { title: "Map Hero Trends | Parsertime", description: "Recent hero pick rates, winrates, and playtime trends by map.", }; -export default async function MapHeroStatsPage() { +// Recent trends are an expensive aggregation that moves slowly; cache them so +// the page prerenders. The service's time-window `Date.now()` resolves once at +// cache fill, which Cache Components permits inside `use cache`. +async function getMapHeroStats() { + "use cache"; + cacheLife("hours"); + const [data, patches] = await Promise.all([ AppRuntime.runPromise( MapHeroTrendsService.pipe( @@ -19,6 +26,11 @@ export default async function MapHeroStatsPage() { ), getOverwatchPatches(), ]); + return { data, patches }; +} + +export default async function MapHeroStatsPage() { + const { data, patches } = await getMapHeroStats(); return ( Date: Fri, 26 Jun 2026 02:56:17 -0400 Subject: [PATCH 09/27] Render the map hero trends page at request time Its data service derives a Date.now() window and localizes map names via the request locale, so it cannot prerender or cache cleanly. Read connection() first to mark it request-time under Cache Components (matching its prior always-dynamic behavior). --- apps/web/src/app/stats/map/page.tsx | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/apps/web/src/app/stats/map/page.tsx b/apps/web/src/app/stats/map/page.tsx index 8a2c8a7af..6fb1a8db7 100644 --- a/apps/web/src/app/stats/map/page.tsx +++ b/apps/web/src/app/stats/map/page.tsx @@ -4,20 +4,18 @@ import { getOverwatchPatches } from "@/data/overwatch/patches-service"; import { AppRuntime } from "@/data/runtime"; import { Effect } from "effect"; import type { Metadata } from "next"; -import { cacheLife } from "next/cache"; +import { connection } from "next/server"; export const metadata: Metadata = { title: "Map Hero Trends | Parsertime", description: "Recent hero pick rates, winrates, and playtime trends by map.", }; -// Recent trends are an expensive aggregation that moves slowly; cache them so -// the page prerenders. The service's time-window `Date.now()` resolves once at -// cache fill, which Cache Components permits inside `use cache`. -async function getMapHeroStats() { - "use cache"; - cacheLife("hours"); - +export default async function MapHeroStatsPage() { + // The trends service derives a time window from Date.now() and localizes map + // names via the request locale, so this page renders at request time. Reading + // the connection first keeps that allowed under Cache Components. + await connection(); const [data, patches] = await Promise.all([ AppRuntime.runPromise( MapHeroTrendsService.pipe( @@ -26,11 +24,6 @@ async function getMapHeroStats() { ), getOverwatchPatches(), ]); - return { data, patches }; -} - -export default async function MapHeroStatsPage() { - const { data, patches } = await getMapHeroStats(); return ( Date: Fri, 26 Jun 2026 03:10:37 -0400 Subject: [PATCH 10/27] Clean up metadata migration types and lint Make the converted generateMetadata functions synchronous (no await), thread the leaderboard relative-time `now` value through PlayerRow, and type the metadata translator against the translator's default options so typecheck and oxlint pass. --- apps/web/src/app/(auth)/auth-error/layout.tsx | 2 +- apps/web/src/app/(auth)/verify-request/layout.tsx | 2 +- apps/web/src/app/chat/layout.tsx | 2 +- apps/web/src/app/coaching/layout.tsx | 2 +- apps/web/src/app/data-labeling/layout.tsx | 2 +- apps/web/src/app/debug/layout.tsx | 2 +- apps/web/src/app/faceit/page.tsx | 2 +- apps/web/src/app/faceit/player/page.tsx | 2 +- apps/web/src/app/leaderboard/csr/page.tsx | 2 +- apps/web/src/app/matchmaker/layout.tsx | 2 +- apps/web/src/app/matchmaker/page.tsx | 2 +- apps/web/src/components/leaderboard/tsr-leaderboard.tsx | 3 +++ apps/web/src/lib/metadata-i18n.ts | 5 ++++- 13 files changed, 18 insertions(+), 12 deletions(-) diff --git a/apps/web/src/app/(auth)/auth-error/layout.tsx b/apps/web/src/app/(auth)/auth-error/layout.tsx index 435a7f6bc..327562839 100644 --- a/apps/web/src/app/(auth)/auth-error/layout.tsx +++ b/apps/web/src/app/(auth)/auth-error/layout.tsx @@ -2,7 +2,7 @@ import { Footer } from "@/components/footer"; import { getMetadataTranslations } from "@/lib/metadata-i18n"; import type { Metadata } from "next"; -export async function generateMetadata(): Promise { +export function generateMetadata(): Metadata { const t = getMetadataTranslations("authError.metadata"); return { title: t("title"), description: t("description") }; } diff --git a/apps/web/src/app/(auth)/verify-request/layout.tsx b/apps/web/src/app/(auth)/verify-request/layout.tsx index 8e4acf340..933cd25d1 100644 --- a/apps/web/src/app/(auth)/verify-request/layout.tsx +++ b/apps/web/src/app/(auth)/verify-request/layout.tsx @@ -2,7 +2,7 @@ import { Footer } from "@/components/footer"; import { getMetadataTranslations } from "@/lib/metadata-i18n"; import type { Metadata } from "next"; -export async function generateMetadata(): Promise { +export function generateMetadata(): Metadata { const t = getMetadataTranslations("verifyRequest.metadata"); return { title: t("title"), description: t("description") }; } diff --git a/apps/web/src/app/chat/layout.tsx b/apps/web/src/app/chat/layout.tsx index 6d19a6058..dc1b1f5a2 100644 --- a/apps/web/src/app/chat/layout.tsx +++ b/apps/web/src/app/chat/layout.tsx @@ -3,7 +3,7 @@ import { DashboardLayout } from "@/components/dashboard-layout"; import { getMetadataTranslations } from "@/lib/metadata-i18n"; import type { Metadata } from "next"; -export async function generateMetadata(): Promise { +export function generateMetadata(): Metadata { const t = getMetadataTranslations("analyst.metadata"); return { title: t("title"), diff --git a/apps/web/src/app/coaching/layout.tsx b/apps/web/src/app/coaching/layout.tsx index f9b00523c..207c3c4ea 100644 --- a/apps/web/src/app/coaching/layout.tsx +++ b/apps/web/src/app/coaching/layout.tsx @@ -2,7 +2,7 @@ import { DashboardLayout } from "@/components/dashboard-layout"; import { getMetadataTranslations } from "@/lib/metadata-i18n"; import type { Metadata } from "next"; -export async function generateMetadata(): Promise { +export function generateMetadata(): Metadata { const t = getMetadataTranslations("coaching.metadata"); return { diff --git a/apps/web/src/app/data-labeling/layout.tsx b/apps/web/src/app/data-labeling/layout.tsx index 20e360d26..ade472529 100644 --- a/apps/web/src/app/data-labeling/layout.tsx +++ b/apps/web/src/app/data-labeling/layout.tsx @@ -2,7 +2,7 @@ import { DashboardLayout } from "@/components/dashboard-layout"; import { getMetadataTranslations } from "@/lib/metadata-i18n"; import type { Metadata } from "next"; -export async function generateMetadata(): Promise { +export function generateMetadata(): Metadata { const t = getMetadataTranslations("dataLabeling.metadata"); return { diff --git a/apps/web/src/app/debug/layout.tsx b/apps/web/src/app/debug/layout.tsx index ceb9ac52f..1d218e4b0 100644 --- a/apps/web/src/app/debug/layout.tsx +++ b/apps/web/src/app/debug/layout.tsx @@ -2,7 +2,7 @@ import { DashboardLayout } from "@/components/dashboard-layout"; import { getMetadataTranslations } from "@/lib/metadata-i18n"; import type { Metadata } from "next"; -export async function generateMetadata(): Promise { +export function generateMetadata(): Metadata { const t = getMetadataTranslations("debugPage.metadata"); return { title: t("title"), description: t("description") }; } diff --git a/apps/web/src/app/faceit/page.tsx b/apps/web/src/app/faceit/page.tsx index 3bb4356b5..f8c8c60f6 100644 --- a/apps/web/src/app/faceit/page.tsx +++ b/apps/web/src/app/faceit/page.tsx @@ -8,7 +8,7 @@ import type { Metadata } from "next"; import { getTranslations } from "next-intl/server"; import { notFound } from "next/navigation"; -export async function generateMetadata(): Promise { +export function generateMetadata(): Metadata { const t = getMetadataTranslations("faceitScoutingPage"); return { title: t("metadata.searchTitle"), description: t("subtitle") }; } diff --git a/apps/web/src/app/faceit/player/page.tsx b/apps/web/src/app/faceit/player/page.tsx index 6c8920e25..ac0b237c5 100644 --- a/apps/web/src/app/faceit/player/page.tsx +++ b/apps/web/src/app/faceit/player/page.tsx @@ -8,7 +8,7 @@ import type { Metadata } from "next"; import { getTranslations } from "next-intl/server"; import { notFound } from "next/navigation"; -export async function generateMetadata(): Promise { +export function generateMetadata(): Metadata { const t = getMetadataTranslations("faceitPlayerPage"); return { title: t("metadata.searchTitle"), description: t("subtitle") }; } diff --git a/apps/web/src/app/leaderboard/csr/page.tsx b/apps/web/src/app/leaderboard/csr/page.tsx index 0494a0cdc..d1a221044 100644 --- a/apps/web/src/app/leaderboard/csr/page.tsx +++ b/apps/web/src/app/leaderboard/csr/page.tsx @@ -14,7 +14,7 @@ import { heroRoleMapping, type HeroName } from "@/types/heroes"; import type { Metadata } from "next"; import { getTranslations } from "next-intl/server"; -export async function generateMetadata(): Promise { +export function generateMetadata(): Metadata { const t = getMetadataTranslations("leaderboardPage.csrPage.metadata"); return { title: t("title"), diff --git a/apps/web/src/app/matchmaker/layout.tsx b/apps/web/src/app/matchmaker/layout.tsx index 0990ed095..3ede0dcfd 100644 --- a/apps/web/src/app/matchmaker/layout.tsx +++ b/apps/web/src/app/matchmaker/layout.tsx @@ -2,7 +2,7 @@ import { DashboardLayout } from "@/components/dashboard-layout"; import { getMetadataTranslations } from "@/lib/metadata-i18n"; import type { Metadata } from "next"; -export async function generateMetadata(): Promise { +export function generateMetadata(): Metadata { const t = getMetadataTranslations("matchmaker.metadata"); return { diff --git a/apps/web/src/app/matchmaker/page.tsx b/apps/web/src/app/matchmaker/page.tsx index 217e12237..3a4fe331b 100644 --- a/apps/web/src/app/matchmaker/page.tsx +++ b/apps/web/src/app/matchmaker/page.tsx @@ -9,7 +9,7 @@ import { getMetadataTranslations } from "@/lib/metadata-i18n"; import { getTierBucket } from "@/lib/tsr/tier-bucket"; import type { Metadata } from "next"; -export async function generateMetadata(): Promise { +export function generateMetadata(): Metadata { const t = getMetadataTranslations("matchmaker.metadata"); return { title: t("title"), diff --git a/apps/web/src/components/leaderboard/tsr-leaderboard.tsx b/apps/web/src/components/leaderboard/tsr-leaderboard.tsx index 0e878dca0..edd481a78 100644 --- a/apps/web/src/components/leaderboard/tsr-leaderboard.tsx +++ b/apps/web/src/components/leaderboard/tsr-leaderboard.tsx @@ -410,6 +410,7 @@ export function TsrLeaderboard({ initialSnapshot }: Props) { showInactive={showInactive} t={t} formatter={formatter} + now={now} /> ))} @@ -511,6 +512,7 @@ function PlayerRow({ showInactive, t, formatter, + now, }: { row: TsrLeaderboardRow; selected: boolean; @@ -518,6 +520,7 @@ function PlayerRow({ showInactive: boolean; t: TsrMessages; formatter: ReturnType; + now: number | null; }) { const isTop = row.rank === 1; const rank = String(row.rank).padStart(2, "0"); diff --git a/apps/web/src/lib/metadata-i18n.ts b/apps/web/src/lib/metadata-i18n.ts index 5b71ea022..763cca7d6 100644 --- a/apps/web/src/lib/metadata-i18n.ts +++ b/apps/web/src/lib/metadata-i18n.ts @@ -13,9 +13,12 @@ import enMessages from "../../messages/en.json"; * `NextIntlClientProvider` in the root layout. */ export function getMetadataTranslations(namespace?: string) { + // The app does not augment next-intl's global `Messages` type, so cast to the + // translator's default (loosely-typed) options instead of having it infer a + // strict namespace union from the imported catalog. return createTranslator({ locale: defaultLocale, messages: enMessages, namespace, - }); + } as Parameters[0]); } From 94fcaa3f6c65c06b63fcb73eb678609f8569a41a Mon Sep 17 00:00:00 2001 From: Lucas Doell Date: Fri, 26 Jun 2026 03:30:21 -0400 Subject: [PATCH 11/27] Tighten Cache Components migration comments --- apps/web/src/app/layout.tsx | 2 +- apps/web/src/app/stats/map/page.tsx | 5 ++--- apps/web/src/components/leaderboard/tsr-leaderboard.tsx | 5 +---- apps/web/src/components/ui/sidebar.tsx | 6 +++--- apps/web/src/instrumentation.ts | 7 +++---- 5 files changed, 10 insertions(+), 15 deletions(-) diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx index f1305c141..6704af7a7 100644 --- a/apps/web/src/app/layout.tsx +++ b/apps/web/src/app/layout.tsx @@ -96,7 +96,7 @@ export default function RootLayout({ children }: LayoutProps<"/">) { > {/* The provider tree depends on request-time data (locale, auth, feature flags), so it streams under Suspense while the document - shell prerenders. See migrating-to-cache-components. */} + shell prerenders. */} {children} diff --git a/apps/web/src/app/stats/map/page.tsx b/apps/web/src/app/stats/map/page.tsx index 6fb1a8db7..ef797e746 100644 --- a/apps/web/src/app/stats/map/page.tsx +++ b/apps/web/src/app/stats/map/page.tsx @@ -12,9 +12,8 @@ export const metadata: Metadata = { }; export default async function MapHeroStatsPage() { - // The trends service derives a time window from Date.now() and localizes map - // names via the request locale, so this page renders at request time. Reading - // the connection first keeps that allowed under Cache Components. + // The trends service reads Date.now() and the request locale, so render at + // request time rather than prerendering. await connection(); const [data, patches] = await Promise.all([ AppRuntime.runPromise( diff --git a/apps/web/src/components/leaderboard/tsr-leaderboard.tsx b/apps/web/src/components/leaderboard/tsr-leaderboard.tsx index edd481a78..3e9bfcccb 100644 --- a/apps/web/src/components/leaderboard/tsr-leaderboard.tsx +++ b/apps/web/src/components/leaderboard/tsr-leaderboard.tsx @@ -61,9 +61,6 @@ const COLS = const PAGE_SIZE = 50; -// `now` is resolved after mount (see the `now` state below) so the prerender -// pass stays deterministic — reading the current time during render is -// disallowed under Cache Components. function formatRelativeDate( d: Date | null, t: TsrMessages, @@ -192,7 +189,7 @@ export function TsrLeaderboard({ initialSnapshot }: Props) { const t = useTranslations("leaderboardPage.tsr"); const formatter = useFormatter(); // Resolved after mount so relative-time labels don't read the clock during - // the prerender pass (disallowed under Cache Components). + // the Cache Components prerender pass. const [now, setNow] = useState(null); useEffect(() => setNow(Date.now()), []); const [region, setRegion] = useState("All"); diff --git a/apps/web/src/components/ui/sidebar.tsx b/apps/web/src/components/ui/sidebar.tsx index a3f5f40af..b21655e70 100644 --- a/apps/web/src/components/ui/sidebar.tsx +++ b/apps/web/src/components/ui/sidebar.tsx @@ -591,9 +591,9 @@ function SidebarMenuSkeleton({ }: React.ComponentProps<"div"> & { showIcon?: boolean; }) { - // Random width between 50 to 90%. Generated after mount so the prerender - // pass stays deterministic (Math.random() during render is disallowed under - // Cache Components) and the server/client markup matches on first paint. + // Random width between 50 to 90%, set after mount so render stays + // deterministic (Math.random() during render is disallowed under Cache + // Components). const [width, setWidth] = React.useState("70%"); React.useEffect(() => { setWidth(`${Math.floor(Math.random() * 40) + 50}%`); diff --git a/apps/web/src/instrumentation.ts b/apps/web/src/instrumentation.ts index 1ca430c22..298bf9a39 100644 --- a/apps/web/src/instrumentation.ts +++ b/apps/web/src/instrumentation.ts @@ -7,10 +7,9 @@ type RequestErrorArgs = Parameters; export async function register() { if (process.env.NEXT_RUNTIME !== "nodejs") return; - // OpenTelemetry generates span/trace IDs with Math.random(), which Cache - // Components disallows during prerendering. Build-time prerendering needs no - // telemetry, so skip tracer setup in the build phase to keep prerenders - // deterministic. Runtime requests still register and export traces. + // OpenTelemetry generates span IDs with Math.random(), which Cache Components + // disallows during prerendering. Build-time prerenders need no telemetry; + // runtime requests still register and export traces. if (process.env.NEXT_PHASE === "phase-production-build") return; const { registerNode } = await import("./instrumentation-node"); From cf3384413d9369b613bfaa036277a680fc45a407 Mon Sep 17 00:00:00 2001 From: Lucas Doell Date: Fri, 26 Jun 2026 03:36:27 -0400 Subject: [PATCH 12/27] Upgrade apps/web to Next.js 16.3 canary Required by the Vercel next-dev-loop and Cache Components skills (16.3+ for the /_next/mcp dev endpoint, export const instant, and instant-nav validation). Build stays green: 0 prerender failures, 92 Partial Prerender routes. --- apps/web/package.json | 2 +- pnpm-lock.yaml | 202 ++++++++++++++++++++++-------------------- 2 files changed, 107 insertions(+), 97 deletions(-) diff --git a/apps/web/package.json b/apps/web/package.json index 58cf25be0..d9dd72b93 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -135,7 +135,7 @@ "media-chrome": "^4.18.2", "motion": "^12.23.26", "nanoid": "^5.1.5", - "next": "16.2.9", + "next": "16.3.0-canary.68", "next-axiom": "^1.9.2", "next-intl": "^4.9.2", "next-seo": "^7.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cb575e691..abe7c38d2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -199,7 +199,7 @@ importers: version: 0.2.2(@axiomhq/js@1.7.0) '@axiomhq/nextjs': specifier: ^0.2.2 - version: 0.2.2(@axiomhq/logging@0.2.2(@axiomhq/js@1.7.0))(next@16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0)) + version: 0.2.2(@axiomhq/logging@0.2.2(@axiomhq/js@1.7.0))(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0)) '@axiomhq/react': specifier: ^0.2.2 version: 0.2.2(@axiomhq/logging@0.2.2(@axiomhq/js@1.7.0))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -223,7 +223,7 @@ importers: version: 0.63.0(@effect/platform@0.96.0(effect@3.21.4))(@opentelemetry/api@1.9.1)(@opentelemetry/resources@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-logs@0.219.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-metrics@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-node@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-web@2.6.0(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.41.1)(effect@3.21.4) '@flags-sdk/vercel': specifier: ^1.0.1 - version: 1.4.2(@aws-sdk/credential-provider-web-identity@3.972.55)(flags@4.2.0(@opentelemetry/api@1.9.1)(next@16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(next@16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(ws@8.21.0) + version: 1.4.2(@aws-sdk/credential-provider-web-identity@3.972.55)(flags@4.2.0(@opentelemetry/api@1.9.1)(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(ws@8.21.0) '@floating-ui/react': specifier: ^0.27.16 version: 0.27.19(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -238,7 +238,7 @@ importers: version: 5.4.0(react-hook-form@7.80.0(react@19.2.3)) '@next/third-parties': specifier: ^16.1.1 - version: 16.2.9(next@16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3) + version: 16.2.9(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3) '@opentelemetry/api': specifier: ^1.9.1 version: 1.9.1 @@ -418,16 +418,16 @@ importers: version: 1.38.0 '@vercel/analytics': specifier: ^2.0.1 - version: 2.0.1(next@16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3) + version: 2.0.1(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3) '@vercel/blob': specifier: ^2.4.1 version: 2.4.1 '@vercel/edge-config': specifier: ^1.4.0 - version: 1.4.3(@opentelemetry/api@1.9.1)(next@16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0)) + version: 1.4.3(@opentelemetry/api@1.9.1)(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0)) '@vercel/flags-core': specifier: ^1.0.1 - version: 1.5.1(@aws-sdk/credential-provider-web-identity@3.972.55)(next@16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(ws@8.21.0) + version: 1.5.1(@aws-sdk/credential-provider-web-identity@3.972.55)(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(ws@8.21.0) '@vercel/functions': specifier: ^3.1.0 version: 3.7.3(@aws-sdk/credential-provider-web-identity@3.972.55)(ws@8.21.0) @@ -436,10 +436,10 @@ importers: version: 3.0.0 '@vercel/speed-insights': specifier: ^1.2.0 - version: 1.3.1(next@16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3) + version: 1.3.1(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3) '@vercel/toolbar': specifier: ^0.2.6 - version: 0.2.6(@vercel/analytics@2.0.1(next@16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3))(@vercel/speed-insights@1.3.1(next@16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3))(next@16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vite@7.3.6(@types/node@20.19.43)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.101.0)(tsx@4.20.5)(yaml@2.9.0)) + version: 0.2.6(@vercel/analytics@2.0.1(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3))(@vercel/speed-insights@1.3.1(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3))(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vite@7.3.6(@types/node@20.19.43)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.101.0)(tsx@4.20.5)(yaml@2.9.0)) '@xstate/store': specifier: ^3.17.1 version: 3.17.5(react@19.2.3) @@ -457,7 +457,7 @@ importers: version: 1.0.0 better-auth: specifier: ^1.6.20 - version: 1.6.20(@opentelemetry/api@1.9.1)(@prisma/client@7.8.0(@typescript/typescript6@6.0.1)(prisma@7.8.0(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(@typescript/typescript6@6.0.1)(magicast@0.5.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)))(mysql2@3.15.3)(next@16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(pg@8.22.0)(prisma@7.8.0(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(@typescript/typescript6@6.0.1)(magicast@0.5.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vitest@3.2.6(@types/debug@4.1.13)(@types/node@20.19.43)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.101.0)(tsx@4.20.5)(yaml@2.9.0)) + version: 1.6.20(@opentelemetry/api@1.9.1)(@prisma/client@7.8.0(@typescript/typescript6@6.0.1)(prisma@7.8.0(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(@typescript/typescript6@6.0.1)(magicast@0.5.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)))(mysql2@3.15.3)(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(pg@8.22.0)(prisma@7.8.0(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(@typescript/typescript6@6.0.1)(magicast@0.5.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vitest@3.2.6(@types/debug@4.1.13)(@types/node@20.19.43)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.101.0)(tsx@4.20.5)(yaml@2.9.0)) cheerio: specifier: ^1.2.0 version: 1.2.0 @@ -484,7 +484,7 @@ importers: version: 8.6.0(react@19.2.3) flags: specifier: ^4.0.3 - version: 4.2.0(@opentelemetry/api@1.9.1)(next@16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + version: 4.2.0(@opentelemetry/api@1.9.1)(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) framer-motion: specifier: ^12.23.13 version: 12.42.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -493,7 +493,7 @@ importers: version: 7.4.2 geist: specifier: ^1.5.1 - version: 1.7.2(next@16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0)) + version: 1.7.2(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0)) input-otp: specifier: ^1.4.2 version: 1.4.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -513,26 +513,26 @@ importers: specifier: ^5.1.5 version: 5.1.16 next: - specifier: 16.2.9 - version: 16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) + specifier: 16.3.0-canary.68 + version: 16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) next-axiom: specifier: ^1.9.2 - version: 1.10.0(@aws-sdk/credential-provider-web-identity@3.972.55)(next@16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3) + version: 1.10.0(@aws-sdk/credential-provider-web-identity@3.972.55)(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3) next-intl: specifier: ^4.9.2 - version: 4.13.0(@swc/helpers@0.5.17)(@typescript/typescript6@6.0.1)(next@16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3) + version: 4.13.0(@swc/helpers@0.5.17)(@typescript/typescript6@6.0.1)(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3) next-seo: specifier: ^7.2.0 - version: 7.2.0(next@16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3) + version: 7.2.0(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3) next-themes: specifier: ^0.4.6 version: 0.4.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) nextstepjs: specifier: ^2.1.2 - version: 2.2.0(motion@12.42.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(next@16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + version: 2.2.0(motion@12.42.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) nuqs: specifier: ^2.8.9 - version: 2.8.9(next@16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3) + version: 2.8.9(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3) pg: specifier: ^8.21.0 version: 8.22.0 @@ -1877,8 +1877,8 @@ packages: '@next/env@16.2.6': resolution: {integrity: sha512-gd8HoHN4ufj73WmR3JmVolrpJR47ILK6LouP5xElPglaVxir6e1a7VzvTvDWkOoPXT9rkkTzyCxBu4yeZfZwcw==} - '@next/env@16.2.9': - resolution: {integrity: sha512-ki5VxxXfzD/9TDe13wyeTKIjQTAwBVpnr8KhRDUr8ltMUq1/NBpWNT5tiPoxiGl+PHM4X2ahSOiPk6iAimIzPg==} + '@next/env@16.3.0-canary.68': + resolution: {integrity: sha512-zNMcEZdRK68jaEwaGNoJ2BogrWrQJx1/lakWvlSm5HWwp0vSFnlq3PgvhjVcjHpQg+JoGrg3UKOlJXlltJZnpQ==} '@next/swc-darwin-arm64@16.2.6': resolution: {integrity: sha512-ZJGkkcNfYgrrMkqOdZ7zoLa1TOy0qpcMfk/z4Mh/FKUz40gVO+HNQWqmLxf67Z5WB64DRp0dhEbyHfel+6sJUg==} @@ -1886,8 +1886,8 @@ packages: cpu: [arm64] os: [darwin] - '@next/swc-darwin-arm64@16.2.9': - resolution: {integrity: sha512-HkfxNYUCmcct0Xsqib5KxqMSHV4AHJq857BNRchyBDs4YS19aHzVfn1kDuBYKqLLQBjXgnkIsjV2Kd4d2wzYhw==} + '@next/swc-darwin-arm64@16.3.0-canary.68': + resolution: {integrity: sha512-qTnqWiDn/RJPnMJMvkBPqqxq4H24qvj6aToq5fzOv67zRbKqAor9HFemUC1zd7pqIJnMbY6WK2gZm+i/YQ/aPw==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] @@ -1898,8 +1898,8 @@ packages: cpu: [x64] os: [darwin] - '@next/swc-darwin-x64@16.2.9': - resolution: {integrity: sha512-7IAtK4MeybpqRV9GRABWEhJ62mOS+rzWOzOTFie4cSEtm12xsoOMJRcECoZx3FHPzFAqN/IJtHqWAFOLfl152w==} + '@next/swc-darwin-x64@16.3.0-canary.68': + resolution: {integrity: sha512-FTKDyZzfoOIALiUX2OrUbf8we1HCOg7lf+jCFnurML8q9kSvzJ3pS1ITNAeZhf52NJHWDF4/JDqFCqRO9wi1uw==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] @@ -1910,8 +1910,8 @@ packages: cpu: [arm64] os: [linux] - '@next/swc-linux-arm64-gnu@16.2.9': - resolution: {integrity: sha512-hBD75iWpUtkL9SmQmcRhmLomn9jgkPzCEkbOcLgHymPEKzv+6ONy13RRiIEz/iEObjkS2Jlb5gYS2XGoS3X4rw==} + '@next/swc-linux-arm64-gnu@16.3.0-canary.68': + resolution: {integrity: sha512-JRatTwMiN91Ny1+CQ59WIp/Fy6t7VfdU2u4dw7w1p60DAfEFPQlYU04G4Jmrm8EP/rWYLBaq5iBDQRGkqs/AEQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] @@ -1922,8 +1922,8 @@ packages: cpu: [arm64] os: [linux] - '@next/swc-linux-arm64-musl@16.2.9': - resolution: {integrity: sha512-qZTI3pf9SGc/obr8NkQAekBxmp1QK+kVm+VAf3BALLfFAj+1kUhkTxmrWpVos9R/UYIA8AWX2p6cGI5WdwzVUA==} + '@next/swc-linux-arm64-musl@16.3.0-canary.68': + resolution: {integrity: sha512-x27TWgIZYISzkbivD7/1BMQ9OkItBpA16I2beOANwBR2E9fT1opt35MEvEBdcAWD8nnKDJ3AJyBBZy4AK+/oeQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] @@ -1934,8 +1934,8 @@ packages: cpu: [x64] os: [linux] - '@next/swc-linux-x64-gnu@16.2.9': - resolution: {integrity: sha512-xm0HfRNX+UkH4R3c18ynswjj5o5uEj/7iI9p9omdtTSIsRCzQqkGMA+10nzJ4EHnYC3as65IMhbbl5fWRUWHYg==} + '@next/swc-linux-x64-gnu@16.3.0-canary.68': + resolution: {integrity: sha512-RalGvF5crlmUcgypBe4X0Ud45BJfhECj39AxvLy+rafPplvaEiAU5Md9h8AdY1awG1X6Gzy9zvScCSDNXONnNg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] @@ -1946,8 +1946,8 @@ packages: cpu: [x64] os: [linux] - '@next/swc-linux-x64-musl@16.2.9': - resolution: {integrity: sha512-QumimHkGEG6vM3PfEDWKyKen03NcqLOkeKB1EfcPe7VxzmEiCa4jNnMyBn/US5zcd/VE1CI+O8Ovb3lfjVHfGw==} + '@next/swc-linux-x64-musl@16.3.0-canary.68': + resolution: {integrity: sha512-cLp55X9LbzaOLV4ar0R10bu6Fyy5RHuA++U1hZlv2WE3hUS57tttfirQugY4nAZXgCgCusolsIaGWbXRV9cAZA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] @@ -1958,8 +1958,8 @@ packages: cpu: [arm64] os: [win32] - '@next/swc-win32-arm64-msvc@16.2.9': - resolution: {integrity: sha512-hzQpKZvw8rAwI6A2uQh6SacCSvNAXaIkPNsWwzqqfRiIMiXMfH936skDhz1OO6KpvdKkJrgHHtqQOq5PIXOvdQ==} + '@next/swc-win32-arm64-msvc@16.3.0-canary.68': + resolution: {integrity: sha512-Xrseocqygkr+AaR8oGHYHgXqmh6FutXAKUq2Rn9E1HxSAXD9UsWLqP/gerQaIH1lbFKUXypJOwifmMA9kcxVsw==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] @@ -1970,8 +1970,8 @@ packages: cpu: [x64] os: [win32] - '@next/swc-win32-x64-msvc@16.2.9': - resolution: {integrity: sha512-qr2VL3Ce5QrwgO2yh1ujSBawrimjVKX8FGF/cOynmdYKJY0BdHpGVNIRK1tqONB10Vkm25Ub1BD2bkjWs4+96w==} + '@next/swc-win32-x64-msvc@16.3.0-canary.68': + resolution: {integrity: sha512-5UyhQAPtCzCbeqR4xgpbCuOoGr2Pg9HmfOMJ+nRRMHAgKSTtl8SDEYZmhBL17gghiWE/lcCpPf8Hk+V/UzURng==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -7557,8 +7557,8 @@ packages: sass: optional: true - next@16.2.9: - resolution: {integrity: sha512-MEOJiq/UvuezAdqVSceHbqDgZt1kDw2tpGVOlsdIoJsQdbN2JY2hpVG4xnXGkbdJUOEWhnRfiu/O4Hpc9Juwww==} + next@16.3.0-canary.68: + resolution: {integrity: sha512-dJaEy6XUchNbOca93QjzaYm7/LpuHd+dxgXi3kR46I2Viuo5bJa98c7Li0fQfE3Jm8XkNLqj6xle6ApbhUtL/g==} engines: {node: '>=20.9.0'} hasBin: true peerDependencies: @@ -7854,6 +7854,10 @@ packages: points-on-path@0.2.1: resolution: {integrity: sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==} + postcss@8.5.10: + resolution: {integrity: sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==} + engines: {node: ^10 || ^12 || >=14} + postcss@8.5.15: resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} engines: {node: ^10 || ^12 || >=14} @@ -9553,10 +9557,10 @@ snapshots: optionalDependencies: '@axiomhq/js': 1.7.0 - '@axiomhq/nextjs@0.2.2(@axiomhq/logging@0.2.2(@axiomhq/js@1.7.0))(next@16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))': + '@axiomhq/nextjs@0.2.2(@axiomhq/logging@0.2.2(@axiomhq/js@1.7.0))(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))': dependencies: '@axiomhq/logging': 0.2.2(@axiomhq/js@1.7.0) - next: 16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) + next: 16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) '@axiomhq/react@0.2.2(@axiomhq/logging@0.2.2(@axiomhq/js@1.7.0))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: @@ -10091,10 +10095,10 @@ snapshots: '@eslint/core': 1.2.1 levn: 0.4.1 - '@flags-sdk/vercel@1.4.2(@aws-sdk/credential-provider-web-identity@3.972.55)(flags@4.2.0(@opentelemetry/api@1.9.1)(next@16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(next@16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(ws@8.21.0)': + '@flags-sdk/vercel@1.4.2(@aws-sdk/credential-provider-web-identity@3.972.55)(flags@4.2.0(@opentelemetry/api@1.9.1)(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(ws@8.21.0)': dependencies: - '@vercel/flags-core': 1.5.1(@aws-sdk/credential-provider-web-identity@3.972.55)(next@16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(ws@8.21.0) - flags: 4.2.0(@opentelemetry/api@1.9.1)(next@16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@vercel/flags-core': 1.5.1(@aws-sdk/credential-provider-web-identity@3.972.55)(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(ws@8.21.0) + flags: 4.2.0(@opentelemetry/api@1.9.1)(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) transitivePeerDependencies: - '@aws-sdk/credential-provider-web-identity' - '@openfeature/server-sdk' @@ -10407,59 +10411,59 @@ snapshots: '@next/env@16.2.6': {} - '@next/env@16.2.9': {} + '@next/env@16.3.0-canary.68': {} '@next/swc-darwin-arm64@16.2.6': optional: true - '@next/swc-darwin-arm64@16.2.9': + '@next/swc-darwin-arm64@16.3.0-canary.68': optional: true '@next/swc-darwin-x64@16.2.6': optional: true - '@next/swc-darwin-x64@16.2.9': + '@next/swc-darwin-x64@16.3.0-canary.68': optional: true '@next/swc-linux-arm64-gnu@16.2.6': optional: true - '@next/swc-linux-arm64-gnu@16.2.9': + '@next/swc-linux-arm64-gnu@16.3.0-canary.68': optional: true '@next/swc-linux-arm64-musl@16.2.6': optional: true - '@next/swc-linux-arm64-musl@16.2.9': + '@next/swc-linux-arm64-musl@16.3.0-canary.68': optional: true '@next/swc-linux-x64-gnu@16.2.6': optional: true - '@next/swc-linux-x64-gnu@16.2.9': + '@next/swc-linux-x64-gnu@16.3.0-canary.68': optional: true '@next/swc-linux-x64-musl@16.2.6': optional: true - '@next/swc-linux-x64-musl@16.2.9': + '@next/swc-linux-x64-musl@16.3.0-canary.68': optional: true '@next/swc-win32-arm64-msvc@16.2.6': optional: true - '@next/swc-win32-arm64-msvc@16.2.9': + '@next/swc-win32-arm64-msvc@16.3.0-canary.68': optional: true '@next/swc-win32-x64-msvc@16.2.6': optional: true - '@next/swc-win32-x64-msvc@16.2.9': + '@next/swc-win32-x64-msvc@16.3.0-canary.68': optional: true - '@next/third-parties@16.2.9(next@16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3)': + '@next/third-parties@16.2.9(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3)': dependencies: - next: 16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) + next: 16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) react: 19.2.3 third-party-capital: 1.0.20 @@ -13711,9 +13715,9 @@ snapshots: '@use-gesture/core': 10.3.1 react: 19.2.7 - '@vercel/analytics@2.0.1(next@16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3)': + '@vercel/analytics@2.0.1(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3)': optionalDependencies: - next: 16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) + next: 16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) react: 19.2.3 '@vercel/blob@2.4.1': @@ -13743,21 +13747,21 @@ snapshots: '@opentelemetry/api': 1.9.1 next: 16.2.6(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0) - '@vercel/edge-config@1.4.3(@opentelemetry/api@1.9.1)(next@16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))': + '@vercel/edge-config@1.4.3(@opentelemetry/api@1.9.1)(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))': dependencies: '@vercel/edge-config-fs': 0.1.0 optionalDependencies: '@opentelemetry/api': 1.9.1 - next: 16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) + next: 16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) - '@vercel/flags-core@1.5.1(@aws-sdk/credential-provider-web-identity@3.972.55)(next@16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(ws@8.21.0)': + '@vercel/flags-core@1.5.1(@aws-sdk/credential-provider-web-identity@3.972.55)(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(ws@8.21.0)': dependencies: '@vercel/functions': 3.7.3(@aws-sdk/credential-provider-web-identity@3.972.55)(ws@8.21.0) '@vercel/oidc': 3.5.0 jose: 5.2.1 js-xxhash: 4.0.0 optionalDependencies: - next: 16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) + next: 16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) transitivePeerDependencies: - '@aws-sdk/credential-provider-web-identity' - ws @@ -13779,7 +13783,7 @@ snapshots: dependencies: '@upstash/redis': 1.38.0 - '@vercel/microfrontends@2.3.6(@vercel/analytics@2.0.1(next@16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3))(@vercel/speed-insights@1.3.1(next@16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3))(next@16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vite@7.3.6(@types/node@20.19.43)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.101.0)(tsx@4.20.5)(yaml@2.9.0))': + '@vercel/microfrontends@2.3.6(@vercel/analytics@2.0.1(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3))(@vercel/speed-insights@1.3.1(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3))(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vite@7.3.6(@types/node@20.19.43)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.101.0)(tsx@4.20.5)(yaml@2.9.0))': dependencies: '@next/env': 16.0.10 '@types/md5': 2.3.6 @@ -13794,9 +13798,9 @@ snapshots: path-to-regexp: 6.3.0 semver: 7.8.5 optionalDependencies: - '@vercel/analytics': 2.0.1(next@16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3) - '@vercel/speed-insights': 1.3.1(next@16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3) - next: 16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) + '@vercel/analytics': 2.0.1(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3) + '@vercel/speed-insights': 1.3.1(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3) + next: 16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) vite: 7.3.6(@types/node@20.19.43)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.101.0)(tsx@4.20.5)(yaml@2.9.0) @@ -13818,22 +13822,22 @@ snapshots: '@vercel/cli-exec': 1.0.0 jose: 5.10.0 - '@vercel/speed-insights@1.3.1(next@16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3)': + '@vercel/speed-insights@1.3.1(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3)': optionalDependencies: - next: 16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) + next: 16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) react: 19.2.3 - '@vercel/toolbar@0.2.6(@vercel/analytics@2.0.1(next@16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3))(@vercel/speed-insights@1.3.1(next@16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3))(next@16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vite@7.3.6(@types/node@20.19.43)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.101.0)(tsx@4.20.5)(yaml@2.9.0))': + '@vercel/toolbar@0.2.6(@vercel/analytics@2.0.1(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3))(@vercel/speed-insights@1.3.1(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3))(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vite@7.3.6(@types/node@20.19.43)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.101.0)(tsx@4.20.5)(yaml@2.9.0))': dependencies: '@tinyhttp/app': 1.3.0 - '@vercel/microfrontends': 2.3.6(@vercel/analytics@2.0.1(next@16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3))(@vercel/speed-insights@1.3.1(next@16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3))(next@16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vite@7.3.6(@types/node@20.19.43)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.101.0)(tsx@4.20.5)(yaml@2.9.0)) + '@vercel/microfrontends': 2.3.6(@vercel/analytics@2.0.1(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3))(@vercel/speed-insights@1.3.1(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3))(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vite@7.3.6(@types/node@20.19.43)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.101.0)(tsx@4.20.5)(yaml@2.9.0)) chokidar: 3.6.0 execa: 5.1.1 find-up: 5.0.0 get-port: 5.1.1 strip-ansi: 6.0.1 optionalDependencies: - next: 16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) + next: 16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) react: 19.2.3 vite: 7.3.6(@types/node@20.19.43)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.101.0)(tsx@4.20.5)(yaml@2.9.0) transitivePeerDependencies: @@ -14031,7 +14035,7 @@ snapshots: baseline-browser-mapping@2.10.38: {} - better-auth@1.6.20(@opentelemetry/api@1.9.1)(@prisma/client@7.8.0(@typescript/typescript6@6.0.1)(prisma@7.8.0(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(@typescript/typescript6@6.0.1)(magicast@0.5.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)))(mysql2@3.15.3)(next@16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(pg@8.22.0)(prisma@7.8.0(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(@typescript/typescript6@6.0.1)(magicast@0.5.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vitest@3.2.6(@types/debug@4.1.13)(@types/node@20.19.43)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.101.0)(tsx@4.20.5)(yaml@2.9.0)): + better-auth@1.6.20(@opentelemetry/api@1.9.1)(@prisma/client@7.8.0(@typescript/typescript6@6.0.1)(prisma@7.8.0(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(@typescript/typescript6@6.0.1)(magicast@0.5.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)))(mysql2@3.15.3)(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(pg@8.22.0)(prisma@7.8.0(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(@typescript/typescript6@6.0.1)(magicast@0.5.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vitest@3.2.6(@types/debug@4.1.13)(@types/node@20.19.43)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.101.0)(tsx@4.20.5)(yaml@2.9.0)): dependencies: '@better-auth/core': 1.6.20(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0) '@better-auth/drizzle-adapter': 1.6.20(@better-auth/core@1.6.20(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.2) @@ -14053,7 +14057,7 @@ snapshots: optionalDependencies: '@prisma/client': 7.8.0(@typescript/typescript6@6.0.1)(prisma@7.8.0(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(@typescript/typescript6@6.0.1)(magicast@0.5.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)) mysql2: 3.15.3 - next: 16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) + next: 16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) pg: 8.22.0 prisma: 7.8.0(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(@typescript/typescript6@6.0.1)(magicast@0.5.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) react: 19.2.3 @@ -15024,13 +15028,13 @@ snapshots: locate-path: 6.0.0 path-exists: 4.0.0 - flags@4.2.0(@opentelemetry/api@1.9.1)(next@16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + flags@4.2.0(@opentelemetry/api@1.9.1)(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: '@edge-runtime/cookies': 5.0.2 jose: 5.10.0 optionalDependencies: '@opentelemetry/api': 1.9.1 - next: 16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) + next: 16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) @@ -15172,9 +15176,9 @@ snapshots: fuse.js@7.4.2: {} - geist@1.7.2(next@16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0)): + geist@1.7.2(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0)): dependencies: - next: 16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) + next: 16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) generate-function@2.3.1: dependencies: @@ -16400,10 +16404,10 @@ snapshots: negotiator@1.0.0: {} - next-axiom@1.10.0(@aws-sdk/credential-provider-web-identity@3.972.55)(next@16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3): + next-axiom@1.10.0(@aws-sdk/credential-provider-web-identity@3.972.55)(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3): dependencies: '@vercel/functions': 2.2.13(@aws-sdk/credential-provider-web-identity@3.972.55) - next: 16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) + next: 16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) react: 19.2.3 use-deep-compare: 1.3.0(react@19.2.3) whatwg-fetch: 3.6.20 @@ -16412,14 +16416,14 @@ snapshots: next-intl-swc-plugin-extractor@4.13.0: {} - next-intl@4.13.0(@swc/helpers@0.5.17)(@typescript/typescript6@6.0.1)(next@16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3): + next-intl@4.13.0(@swc/helpers@0.5.17)(@typescript/typescript6@6.0.1)(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3): dependencies: '@formatjs/intl-localematcher': 0.8.10 '@parcel/watcher': 2.5.1 '@swc/core': 1.15.43(@swc/helpers@0.5.17) icu-minify: 4.13.0 negotiator: 1.0.0 - next: 16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) + next: 16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) next-intl-swc-plugin-extractor: 4.13.0 po-parser: 2.1.1 react: 19.2.3 @@ -16434,9 +16438,9 @@ snapshots: next: 16.2.6(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0) react: 19.2.7 - next-seo@7.2.0(next@16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3): + next-seo@7.2.0(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3): dependencies: - next: 16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) + next: 16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) react: 19.2.3 next-themes@0.4.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3): @@ -16503,25 +16507,25 @@ snapshots: - '@babel/core' - babel-plugin-macros - next@16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0): + next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0): dependencies: - '@next/env': 16.2.9 + '@next/env': 16.3.0-canary.68 '@swc/helpers': 0.5.15 baseline-browser-mapping: 2.10.38 caniuse-lite: 1.0.30001799 - postcss: 8.5.15 + postcss: 8.5.10 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) styled-jsx: 5.1.6(@babel/core@7.29.7)(react@19.2.3) optionalDependencies: - '@next/swc-darwin-arm64': 16.2.9 - '@next/swc-darwin-x64': 16.2.9 - '@next/swc-linux-arm64-gnu': 16.2.9 - '@next/swc-linux-arm64-musl': 16.2.9 - '@next/swc-linux-x64-gnu': 16.2.9 - '@next/swc-linux-x64-musl': 16.2.9 - '@next/swc-win32-arm64-msvc': 16.2.9 - '@next/swc-win32-x64-msvc': 16.2.9 + '@next/swc-darwin-arm64': 16.3.0-canary.68 + '@next/swc-darwin-x64': 16.3.0-canary.68 + '@next/swc-linux-arm64-gnu': 16.3.0-canary.68 + '@next/swc-linux-arm64-musl': 16.3.0-canary.68 + '@next/swc-linux-x64-gnu': 16.3.0-canary.68 + '@next/swc-linux-x64-musl': 16.3.0-canary.68 + '@next/swc-win32-arm64-msvc': 16.3.0-canary.68 + '@next/swc-win32-x64-msvc': 16.3.0-canary.68 '@opentelemetry/api': 1.9.1 babel-plugin-react-compiler: 1.0.0 sass: 1.101.0 @@ -16530,13 +16534,13 @@ snapshots: - '@babel/core' - babel-plugin-macros - nextstepjs@2.2.0(motion@12.42.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(next@16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + nextstepjs@2.2.0(motion@12.42.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: motion: 12.42.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) optionalDependencies: - next: 16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) + next: 16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) node-addon-api@7.1.1: {} @@ -16559,12 +16563,12 @@ snapshots: dependencies: boolbase: 1.0.0 - nuqs@2.8.9(next@16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3): + nuqs@2.8.9(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3): dependencies: '@standard-schema/spec': 1.0.0 react: 19.2.3 optionalDependencies: - next: 16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) + next: 16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) nypm@0.6.6: dependencies: @@ -16899,6 +16903,12 @@ snapshots: path-data-parser: 0.1.0 points-on-curve: 0.2.0 + postcss@8.5.10: + dependencies: + nanoid: 3.3.15 + picocolors: 1.1.1 + source-map-js: 1.2.1 + postcss@8.5.15: dependencies: nanoid: 3.3.15 From fe432fb931292144f3ae1d1006d220cf8ee2af39 Mon Sep 17 00:00:00 2001 From: Lucas Doell Date: Fri, 26 Jun 2026 03:53:44 -0400 Subject: [PATCH 13/27] Enable partial prefetching and stream the map trends page Switch to next@preview (16.3) and turn on partialPrefetching for instant navigations. Replace the map hero trends page's full-page connection() block with a Suspense boundary + content-shaped fallback so the route shows an instant shell on navigation while the trends stream in. --- apps/web/AGENTS.md | 10 +- apps/web/next.config.ts | 1 + apps/web/package.json | 2 +- apps/web/src/app/stats/map/page.tsx | 31 ++++- pnpm-lock.yaml | 190 ++++++++++++++-------------- 5 files changed, 135 insertions(+), 99 deletions(-) diff --git a/apps/web/AGENTS.md b/apps/web/AGENTS.md index 08f447d41..e99b55890 100644 --- a/apps/web/AGENTS.md +++ b/apps/web/AGENTS.md @@ -1,5 +1,3 @@ -[Next.js Docs Index]|root: ./.next-docs|STOP. What you remember about Next.js is WRONG for this project. Always search docs and read before any task.|If docs missing, run this command first: npx @next/codemod agents-md --output CLAUDE.md|01-app:{04-glossary.mdx}|01-app/01-getting-started:{01-installation.mdx,02-project-structure.mdx,03-layouts-and-pages.mdx,04-linking-and-navigating.mdx,05-server-and-client-components.mdx,06-fetching-data.mdx,07-mutating-data.mdx,08-caching.mdx,09-revalidating.mdx,10-error-handling.mdx,11-css.mdx,12-images.mdx,13-fonts.mdx,14-metadata-and-og-images.mdx,15-route-handlers.mdx,16-proxy.mdx,17-deploying.mdx,18-upgrading.mdx}|01-app/02-guides:{ai-agents.mdx,analytics.mdx,authentication.mdx,backend-for-frontend.mdx,caching-without-cache-components.mdx,ci-build-caching.mdx,content-security-policy.mdx,css-in-js.mdx,custom-server.mdx,data-security.mdx,debugging.mdx,draft-mode.mdx,environment-variables.mdx,forms.mdx,incremental-static-regeneration.mdx,instant-navigation.mdx,instrumentation.mdx,internationalization.mdx,json-ld.mdx,lazy-loading.mdx,local-development.mdx,mcp.mdx,mdx.mdx,memory-usage.mdx,migrating-to-cache-components.mdx,multi-tenant.mdx,multi-zones.mdx,open-telemetry.mdx,package-bundling.mdx,prefetching.mdx,preserving-ui-state.mdx,production-checklist.mdx,progressive-web-apps.mdx,public-static-pages.mdx,redirecting.mdx,sass.mdx,scripts.mdx,self-hosting.mdx,single-page-applications.mdx,static-exports.mdx,streaming.mdx,tailwind-v3-css.mdx,third-party-libraries.mdx,videos.mdx}|01-app/02-guides/migrating:{app-router-migration.mdx,from-create-react-app.mdx,from-vite.mdx}|01-app/02-guides/testing:{cypress.mdx,jest.mdx,playwright.mdx,vitest.mdx}|01-app/02-guides/upgrading:{codemods.mdx,version-14.mdx,version-15.mdx,version-16.mdx}|01-app/03-api-reference:{07-edge.mdx,08-turbopack.mdx}|01-app/03-api-reference/01-directives:{use-cache-private.mdx,use-cache-remote.mdx,use-cache.mdx,use-client.mdx,use-server.mdx}|01-app/03-api-reference/02-components:{font.mdx,form.mdx,image.mdx,link.mdx,script.mdx}|01-app/03-api-reference/03-file-conventions/01-metadata:{app-icons.mdx,manifest.mdx,opengraph-image.mdx,robots.mdx,sitemap.mdx}|01-app/03-api-reference/03-file-conventions/02-route-segment-config:{dynamicParams.mdx,instant.mdx,maxDuration.mdx,preferredRegion.mdx,runtime.mdx}|01-app/03-api-reference/03-file-conventions:{default.mdx,dynamic-routes.mdx,error.mdx,forbidden.mdx,instrumentation-client.mdx,instrumentation.mdx,intercepting-routes.mdx,layout.mdx,loading.mdx,mdx-components.mdx,not-found.mdx,page.mdx,parallel-routes.mdx,proxy.mdx,public-folder.mdx,route-groups.mdx,route.mdx,src-folder.mdx,template.mdx,unauthorized.mdx}|01-app/03-api-reference/04-functions:{after.mdx,cacheLife.mdx,cacheTag.mdx,catchError.mdx,connection.mdx,cookies.mdx,draft-mode.mdx,fetch.mdx,forbidden.mdx,generate-image-metadata.mdx,generate-metadata.mdx,generate-sitemaps.mdx,generate-static-params.mdx,generate-viewport.mdx,headers.mdx,image-response.mdx,next-request.mdx,next-response.mdx,not-found.mdx,permanentRedirect.mdx,redirect.mdx,refresh.mdx,revalidatePath.mdx,revalidateTag.mdx,unauthorized.mdx,unstable_cache.mdx,unstable_noStore.mdx,unstable_rethrow.mdx,updateTag.mdx,use-link-status.mdx,use-params.mdx,use-pathname.mdx,use-report-web-vitals.mdx,use-router.mdx,use-search-params.mdx,use-selected-layout-segment.mdx,use-selected-layout-segments.mdx,userAgent.mdx}|01-app/03-api-reference/05-config/01-next-config-js:{adapterPath.mdx,allowedDevOrigins.mdx,appDir.mdx,assetPrefix.mdx,authInterrupts.mdx,basePath.mdx,cacheComponents.mdx,cacheHandlers.mdx,cacheLife.mdx,compress.mdx,crossOrigin.mdx,cssChunking.mdx,deploymentId.mdx,devIndicators.mdx,distDir.mdx,env.mdx,expireTime.mdx,exportPathMap.mdx,generateBuildId.mdx,generateEtags.mdx,headers.mdx,htmlLimitedBots.mdx,httpAgentOptions.mdx,images.mdx,incrementalCacheHandlerPath.mdx,inlineCss.mdx,logging.mdx,mdxRs.mdx,onDemandEntries.mdx,optimizePackageImports.mdx,output.mdx,pageExtensions.mdx,poweredByHeader.mdx,productionBrowserSourceMaps.mdx,proxyClientMaxBodySize.mdx,reactCompiler.mdx,reactMaxHeadersLength.mdx,reactStrictMode.mdx,redirects.mdx,rewrites.mdx,sassOptions.mdx,serverActions.mdx,serverComponentsHmrCache.mdx,serverExternalPackages.mdx,staleTimes.mdx,staticGeneration.mdx,taint.mdx,trailingSlash.mdx,transpilePackages.mdx,turbopack.mdx,turbopackFileSystemCache.mdx,turbopackIgnoreIssue.mdx,typedRoutes.mdx,typescript.mdx,urlImports.mdx,useLightningcss.mdx,viewTransition.mdx,webVitalsAttribution.mdx,webpack.mdx}|01-app/03-api-reference/05-config:{02-typescript.mdx,03-eslint.mdx}|01-app/03-api-reference/06-cli:{create-next-app.mdx,next.mdx}|02-pages/01-getting-started:{01-installation.mdx,02-project-structure.mdx,04-images.mdx,05-fonts.mdx,06-css.mdx,11-deploying.mdx}|02-pages/02-guides:{analytics.mdx,authentication.mdx,babel.mdx,ci-build-caching.mdx,content-security-policy.mdx,css-in-js.mdx,custom-server.mdx,debugging.mdx,draft-mode.mdx,environment-variables.mdx,forms.mdx,incremental-static-regeneration.mdx,instrumentation.mdx,internationalization.mdx,lazy-loading.mdx,mdx.mdx,multi-zones.mdx,open-telemetry.mdx,package-bundling.mdx,post-css.mdx,preview-mode.mdx,production-checklist.mdx,redirecting.mdx,sass.mdx,scripts.mdx,self-hosting.mdx,static-exports.mdx,tailwind-v3-css.mdx,third-party-libraries.mdx}|02-pages/02-guides/migrating:{app-router-migration.mdx,from-create-react-app.mdx,from-vite.mdx}|02-pages/02-guides/testing:{cypress.mdx,jest.mdx,playwright.mdx,vitest.mdx}|02-pages/02-guides/upgrading:{codemods.mdx,version-10.mdx,version-11.mdx,version-12.mdx,version-13.mdx,version-14.mdx,version-9.mdx}|02-pages/03-building-your-application/01-routing:{01-pages-and-layouts.mdx,02-dynamic-routes.mdx,03-linking-and-navigating.mdx,05-custom-app.mdx,06-custom-document.mdx,07-api-routes.mdx,08-custom-error.mdx}|02-pages/03-building-your-application/02-rendering:{01-server-side-rendering.mdx,02-static-site-generation.mdx,04-automatic-static-optimization.mdx,05-client-side-rendering.mdx}|02-pages/03-building-your-application/03-data-fetching:{01-get-static-props.mdx,02-get-static-paths.mdx,03-forms-and-mutations.mdx,03-get-server-side-props.mdx,05-client-side.mdx}|02-pages/03-building-your-application/06-configuring:{12-error-handling.mdx}|02-pages/04-api-reference:{06-edge.mdx,08-turbopack.mdx}|02-pages/04-api-reference/01-components:{font.mdx,form.mdx,head.mdx,image-legacy.mdx,image.mdx,link.mdx,script.mdx}|02-pages/04-api-reference/02-file-conventions:{instrumentation.mdx,proxy.mdx,public-folder.mdx,src-folder.mdx}|02-pages/04-api-reference/03-functions:{get-initial-props.mdx,get-server-side-props.mdx,get-static-paths.mdx,get-static-props.mdx,next-request.mdx,next-response.mdx,use-params.mdx,use-report-web-vitals.mdx,use-router.mdx,use-search-params.mdx,userAgent.mdx}|02-pages/04-api-reference/04-config/01-next-config-js:{adapterPath.mdx,allowedDevOrigins.mdx,assetPrefix.mdx,basePath.mdx,bundlePagesRouterDependencies.mdx,compress.mdx,crossOrigin.mdx,deploymentId.mdx,devIndicators.mdx,distDir.mdx,env.mdx,exportPathMap.mdx,generateBuildId.mdx,generateEtags.mdx,headers.mdx,httpAgentOptions.mdx,images.mdx,logging.mdx,onDemandEntries.mdx,optimizePackageImports.mdx,output.mdx,pageExtensions.mdx,poweredByHeader.mdx,productionBrowserSourceMaps.mdx,proxyClientMaxBodySize.mdx,reactStrictMode.mdx,redirects.mdx,rewrites.mdx,serverExternalPackages.mdx,trailingSlash.mdx,transpilePackages.mdx,turbopack.mdx,typescript.mdx,urlImports.mdx,useLightningcss.mdx,webVitalsAttribution.mdx,webpack.mdx}|02-pages/04-api-reference/04-config:{01-typescript.mdx,02-eslint.mdx}|02-pages/04-api-reference/05-cli:{create-next-app.mdx,next.mdx}|03-architecture:{accessibility.mdx,fast-refresh.mdx,nextjs-compiler.mdx,supported-browsers.mdx}|04-community:{01-contribution-guide.mdx,02-rspack.mdx} - ## Internationalization The Next.js app uses `next-intl` for internationalization, with `en.json`, `ko.json`, and `zh.json` files living at `apps/web/messages`. When building user-facing components, make sure to include proper translation support. @@ -21,3 +19,11 @@ Canonical defaults (`needs-triage`, `needs-info`, `ready-for-agent`, `ready-for- ### Domain docs Single-context: one `CONTEXT.md` + `docs/adr/` at the repo root (created lazily by `/grill-with-docs`). See `docs/agents/domain.md`. + + +# This is NOT the Next.js you know + +This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices. + +**Keep this block, including in commits.** It is part of the project's agent setup, maintained by `next dev` for every agent that works here. If it appears as an uncommitted change, that is intentional — commit it as-is. Do not remove it to clean up a diff; it will be regenerated. + diff --git a/apps/web/next.config.ts b/apps/web/next.config.ts index 9104b5ba0..6881b833a 100644 --- a/apps/web/next.config.ts +++ b/apps/web/next.config.ts @@ -26,6 +26,7 @@ const cspHeader = ` const nextConfig: NextConfig = { cacheComponents: true, + partialPrefetching: true, reactCompiler: true, typedRoutes: true, transpilePackages: ["@parsertime/transactional"], diff --git a/apps/web/package.json b/apps/web/package.json index d9dd72b93..0d90aa7b1 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -135,7 +135,7 @@ "media-chrome": "^4.18.2", "motion": "^12.23.26", "nanoid": "^5.1.5", - "next": "16.3.0-canary.68", + "next": "16.3.0-preview.5", "next-axiom": "^1.9.2", "next-intl": "^4.9.2", "next-seo": "^7.2.0", diff --git a/apps/web/src/app/stats/map/page.tsx b/apps/web/src/app/stats/map/page.tsx index ef797e746..b7caf0d27 100644 --- a/apps/web/src/app/stats/map/page.tsx +++ b/apps/web/src/app/stats/map/page.tsx @@ -1,17 +1,27 @@ import { MapHeroTrends } from "@/components/stats/map/map-hero-trends"; +import { Skeleton } from "@/components/ui/skeleton"; import { MapHeroTrendsService } from "@/data/map"; import { getOverwatchPatches } from "@/data/overwatch/patches-service"; import { AppRuntime } from "@/data/runtime"; import { Effect } from "effect"; import type { Metadata } from "next"; import { connection } from "next/server"; +import { Suspense } from "react"; export const metadata: Metadata = { title: "Map Hero Trends | Parsertime", description: "Recent hero pick rates, winrates, and playtime trends by map.", }; -export default async function MapHeroStatsPage() { +export default function MapHeroStatsPage() { + return ( + }> + + + ); +} + +async function MapHeroTrendsContent() { // The trends service reads Date.now() and the request locale, so render at // request time rather than prerendering. await connection(); @@ -31,3 +41,22 @@ export default async function MapHeroStatsPage() { /> ); } + +function MapHeroTrendsFallback() { + return ( +
+
+
+ + + +
+
+
+ {["a", "b", "c", "d", "e", "f"].map((key) => ( + + ))} +
+
+ ); +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index abe7c38d2..231c080ca 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -199,7 +199,7 @@ importers: version: 0.2.2(@axiomhq/js@1.7.0) '@axiomhq/nextjs': specifier: ^0.2.2 - version: 0.2.2(@axiomhq/logging@0.2.2(@axiomhq/js@1.7.0))(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0)) + version: 0.2.2(@axiomhq/logging@0.2.2(@axiomhq/js@1.7.0))(next@16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0)) '@axiomhq/react': specifier: ^0.2.2 version: 0.2.2(@axiomhq/logging@0.2.2(@axiomhq/js@1.7.0))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -223,7 +223,7 @@ importers: version: 0.63.0(@effect/platform@0.96.0(effect@3.21.4))(@opentelemetry/api@1.9.1)(@opentelemetry/resources@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-logs@0.219.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-metrics@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-node@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-web@2.6.0(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.41.1)(effect@3.21.4) '@flags-sdk/vercel': specifier: ^1.0.1 - version: 1.4.2(@aws-sdk/credential-provider-web-identity@3.972.55)(flags@4.2.0(@opentelemetry/api@1.9.1)(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(ws@8.21.0) + version: 1.4.2(@aws-sdk/credential-provider-web-identity@3.972.55)(flags@4.2.0(@opentelemetry/api@1.9.1)(next@16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(next@16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(ws@8.21.0) '@floating-ui/react': specifier: ^0.27.16 version: 0.27.19(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -238,7 +238,7 @@ importers: version: 5.4.0(react-hook-form@7.80.0(react@19.2.3)) '@next/third-parties': specifier: ^16.1.1 - version: 16.2.9(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3) + version: 16.2.9(next@16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3) '@opentelemetry/api': specifier: ^1.9.1 version: 1.9.1 @@ -418,16 +418,16 @@ importers: version: 1.38.0 '@vercel/analytics': specifier: ^2.0.1 - version: 2.0.1(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3) + version: 2.0.1(next@16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3) '@vercel/blob': specifier: ^2.4.1 version: 2.4.1 '@vercel/edge-config': specifier: ^1.4.0 - version: 1.4.3(@opentelemetry/api@1.9.1)(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0)) + version: 1.4.3(@opentelemetry/api@1.9.1)(next@16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0)) '@vercel/flags-core': specifier: ^1.0.1 - version: 1.5.1(@aws-sdk/credential-provider-web-identity@3.972.55)(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(ws@8.21.0) + version: 1.5.1(@aws-sdk/credential-provider-web-identity@3.972.55)(next@16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(ws@8.21.0) '@vercel/functions': specifier: ^3.1.0 version: 3.7.3(@aws-sdk/credential-provider-web-identity@3.972.55)(ws@8.21.0) @@ -436,10 +436,10 @@ importers: version: 3.0.0 '@vercel/speed-insights': specifier: ^1.2.0 - version: 1.3.1(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3) + version: 1.3.1(next@16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3) '@vercel/toolbar': specifier: ^0.2.6 - version: 0.2.6(@vercel/analytics@2.0.1(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3))(@vercel/speed-insights@1.3.1(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3))(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vite@7.3.6(@types/node@20.19.43)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.101.0)(tsx@4.20.5)(yaml@2.9.0)) + version: 0.2.6(@vercel/analytics@2.0.1(next@16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3))(@vercel/speed-insights@1.3.1(next@16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3))(next@16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vite@7.3.6(@types/node@20.19.43)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.101.0)(tsx@4.20.5)(yaml@2.9.0)) '@xstate/store': specifier: ^3.17.1 version: 3.17.5(react@19.2.3) @@ -457,7 +457,7 @@ importers: version: 1.0.0 better-auth: specifier: ^1.6.20 - version: 1.6.20(@opentelemetry/api@1.9.1)(@prisma/client@7.8.0(@typescript/typescript6@6.0.1)(prisma@7.8.0(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(@typescript/typescript6@6.0.1)(magicast@0.5.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)))(mysql2@3.15.3)(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(pg@8.22.0)(prisma@7.8.0(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(@typescript/typescript6@6.0.1)(magicast@0.5.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vitest@3.2.6(@types/debug@4.1.13)(@types/node@20.19.43)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.101.0)(tsx@4.20.5)(yaml@2.9.0)) + version: 1.6.20(@opentelemetry/api@1.9.1)(@prisma/client@7.8.0(@typescript/typescript6@6.0.1)(prisma@7.8.0(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(@typescript/typescript6@6.0.1)(magicast@0.5.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)))(mysql2@3.15.3)(next@16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(pg@8.22.0)(prisma@7.8.0(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(@typescript/typescript6@6.0.1)(magicast@0.5.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vitest@3.2.6(@types/debug@4.1.13)(@types/node@20.19.43)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.101.0)(tsx@4.20.5)(yaml@2.9.0)) cheerio: specifier: ^1.2.0 version: 1.2.0 @@ -484,7 +484,7 @@ importers: version: 8.6.0(react@19.2.3) flags: specifier: ^4.0.3 - version: 4.2.0(@opentelemetry/api@1.9.1)(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + version: 4.2.0(@opentelemetry/api@1.9.1)(next@16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) framer-motion: specifier: ^12.23.13 version: 12.42.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -493,7 +493,7 @@ importers: version: 7.4.2 geist: specifier: ^1.5.1 - version: 1.7.2(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0)) + version: 1.7.2(next@16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0)) input-otp: specifier: ^1.4.2 version: 1.4.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -513,26 +513,26 @@ importers: specifier: ^5.1.5 version: 5.1.16 next: - specifier: 16.3.0-canary.68 - version: 16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) + specifier: 16.3.0-preview.5 + version: 16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) next-axiom: specifier: ^1.9.2 - version: 1.10.0(@aws-sdk/credential-provider-web-identity@3.972.55)(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3) + version: 1.10.0(@aws-sdk/credential-provider-web-identity@3.972.55)(next@16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3) next-intl: specifier: ^4.9.2 - version: 4.13.0(@swc/helpers@0.5.17)(@typescript/typescript6@6.0.1)(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3) + version: 4.13.0(@swc/helpers@0.5.17)(@typescript/typescript6@6.0.1)(next@16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3) next-seo: specifier: ^7.2.0 - version: 7.2.0(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3) + version: 7.2.0(next@16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3) next-themes: specifier: ^0.4.6 version: 0.4.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) nextstepjs: specifier: ^2.1.2 - version: 2.2.0(motion@12.42.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + version: 2.2.0(motion@12.42.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(next@16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) nuqs: specifier: ^2.8.9 - version: 2.8.9(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3) + version: 2.8.9(next@16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3) pg: specifier: ^8.21.0 version: 8.22.0 @@ -1877,8 +1877,8 @@ packages: '@next/env@16.2.6': resolution: {integrity: sha512-gd8HoHN4ufj73WmR3JmVolrpJR47ILK6LouP5xElPglaVxir6e1a7VzvTvDWkOoPXT9rkkTzyCxBu4yeZfZwcw==} - '@next/env@16.3.0-canary.68': - resolution: {integrity: sha512-zNMcEZdRK68jaEwaGNoJ2BogrWrQJx1/lakWvlSm5HWwp0vSFnlq3PgvhjVcjHpQg+JoGrg3UKOlJXlltJZnpQ==} + '@next/env@16.3.0-preview.5': + resolution: {integrity: sha512-XqdVR0utAWMsVc1OIyO48D32vrdmC4/uAgI3Ds088YlOO4vfGKXXVyvkGFkOZkOK0xg7bNYNfJAarX4A0tYqGg==} '@next/swc-darwin-arm64@16.2.6': resolution: {integrity: sha512-ZJGkkcNfYgrrMkqOdZ7zoLa1TOy0qpcMfk/z4Mh/FKUz40gVO+HNQWqmLxf67Z5WB64DRp0dhEbyHfel+6sJUg==} @@ -1886,8 +1886,8 @@ packages: cpu: [arm64] os: [darwin] - '@next/swc-darwin-arm64@16.3.0-canary.68': - resolution: {integrity: sha512-qTnqWiDn/RJPnMJMvkBPqqxq4H24qvj6aToq5fzOv67zRbKqAor9HFemUC1zd7pqIJnMbY6WK2gZm+i/YQ/aPw==} + '@next/swc-darwin-arm64@16.3.0-preview.5': + resolution: {integrity: sha512-PPWAJGoIkzVpz5hOD9V/qGNdkBuWj3QXhjQU8BQ1FXlMy6xsy4+aD/3UoasKy/HYInW4h1LqdQtDhiQkLYrrMA==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] @@ -1898,8 +1898,8 @@ packages: cpu: [x64] os: [darwin] - '@next/swc-darwin-x64@16.3.0-canary.68': - resolution: {integrity: sha512-FTKDyZzfoOIALiUX2OrUbf8we1HCOg7lf+jCFnurML8q9kSvzJ3pS1ITNAeZhf52NJHWDF4/JDqFCqRO9wi1uw==} + '@next/swc-darwin-x64@16.3.0-preview.5': + resolution: {integrity: sha512-UPN/RS1H+kr9fgJrbFoH7bs1b9q2/G5cFe+uUf0nP4Hlgfl8NzfTBHEJKTfLAGqi1Qemwuyd29pvRy2vwEjL5g==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] @@ -1910,8 +1910,8 @@ packages: cpu: [arm64] os: [linux] - '@next/swc-linux-arm64-gnu@16.3.0-canary.68': - resolution: {integrity: sha512-JRatTwMiN91Ny1+CQ59WIp/Fy6t7VfdU2u4dw7w1p60DAfEFPQlYU04G4Jmrm8EP/rWYLBaq5iBDQRGkqs/AEQ==} + '@next/swc-linux-arm64-gnu@16.3.0-preview.5': + resolution: {integrity: sha512-kh+bKgk9ZIlmxMkEPnQZXtKc7/AyUyIS9jXgbKt4hWyxXEEZVDmXhiU2bh1zZpthMr/l09wz9z6CvfXtCWUJBA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] @@ -1922,8 +1922,8 @@ packages: cpu: [arm64] os: [linux] - '@next/swc-linux-arm64-musl@16.3.0-canary.68': - resolution: {integrity: sha512-x27TWgIZYISzkbivD7/1BMQ9OkItBpA16I2beOANwBR2E9fT1opt35MEvEBdcAWD8nnKDJ3AJyBBZy4AK+/oeQ==} + '@next/swc-linux-arm64-musl@16.3.0-preview.5': + resolution: {integrity: sha512-m09/acXFGhlp+U6m7Wn0AqsmLqars3qI9eBXDpPJm4h/XVS9HPHNzWGy2BI7F1iLoFX59Uy0tcau9ey7JVud3w==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] @@ -1934,8 +1934,8 @@ packages: cpu: [x64] os: [linux] - '@next/swc-linux-x64-gnu@16.3.0-canary.68': - resolution: {integrity: sha512-RalGvF5crlmUcgypBe4X0Ud45BJfhECj39AxvLy+rafPplvaEiAU5Md9h8AdY1awG1X6Gzy9zvScCSDNXONnNg==} + '@next/swc-linux-x64-gnu@16.3.0-preview.5': + resolution: {integrity: sha512-/EBiqRjLZJWJo6Keq9upJfhrP+tNpePy1beBfOL+tUn68inwNiJEjx+0Lgve99Zur8kSk9TgSmDmwgQxX4iM+g==} engines: {node: '>= 10'} cpu: [x64] os: [linux] @@ -1946,8 +1946,8 @@ packages: cpu: [x64] os: [linux] - '@next/swc-linux-x64-musl@16.3.0-canary.68': - resolution: {integrity: sha512-cLp55X9LbzaOLV4ar0R10bu6Fyy5RHuA++U1hZlv2WE3hUS57tttfirQugY4nAZXgCgCusolsIaGWbXRV9cAZA==} + '@next/swc-linux-x64-musl@16.3.0-preview.5': + resolution: {integrity: sha512-lUCiPFoecSGkM8aeY6UAgQDiJjR3DhPsI036mznlHFg89ZLoeRdo521N4nmk6EpbPpNzRujgiboBkbuyexDgCg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] @@ -1958,8 +1958,8 @@ packages: cpu: [arm64] os: [win32] - '@next/swc-win32-arm64-msvc@16.3.0-canary.68': - resolution: {integrity: sha512-Xrseocqygkr+AaR8oGHYHgXqmh6FutXAKUq2Rn9E1HxSAXD9UsWLqP/gerQaIH1lbFKUXypJOwifmMA9kcxVsw==} + '@next/swc-win32-arm64-msvc@16.3.0-preview.5': + resolution: {integrity: sha512-Nr4e3dRB86gElIgysL/L7dr9tuRLIq3looK8hLxnYDLUvLza2Tu/7Ik/X6DSRGejIrbZsYjnH3S4xYeAAf7Prw==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] @@ -1970,8 +1970,8 @@ packages: cpu: [x64] os: [win32] - '@next/swc-win32-x64-msvc@16.3.0-canary.68': - resolution: {integrity: sha512-5UyhQAPtCzCbeqR4xgpbCuOoGr2Pg9HmfOMJ+nRRMHAgKSTtl8SDEYZmhBL17gghiWE/lcCpPf8Hk+V/UzURng==} + '@next/swc-win32-x64-msvc@16.3.0-preview.5': + resolution: {integrity: sha512-Svg+VCRUbyNsBuh96hN+1ael8dNXqVQVZqOe9tqFlF4mUzIk5CQFcn5VsZPrz8GNP9HCxJfrfy3PM0cXoSXliw==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -7557,8 +7557,8 @@ packages: sass: optional: true - next@16.3.0-canary.68: - resolution: {integrity: sha512-dJaEy6XUchNbOca93QjzaYm7/LpuHd+dxgXi3kR46I2Viuo5bJa98c7Li0fQfE3Jm8XkNLqj6xle6ApbhUtL/g==} + next@16.3.0-preview.5: + resolution: {integrity: sha512-I5rVC4VcvAL1FPr6AY5WEQUSe6o1Bt0Oa/qH5hfPhci4FRMCPeAQ95tgxFOgJDk2wME1K009k0bjS17nQ0Bq1w==} engines: {node: '>=20.9.0'} hasBin: true peerDependencies: @@ -9557,10 +9557,10 @@ snapshots: optionalDependencies: '@axiomhq/js': 1.7.0 - '@axiomhq/nextjs@0.2.2(@axiomhq/logging@0.2.2(@axiomhq/js@1.7.0))(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))': + '@axiomhq/nextjs@0.2.2(@axiomhq/logging@0.2.2(@axiomhq/js@1.7.0))(next@16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))': dependencies: '@axiomhq/logging': 0.2.2(@axiomhq/js@1.7.0) - next: 16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) + next: 16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) '@axiomhq/react@0.2.2(@axiomhq/logging@0.2.2(@axiomhq/js@1.7.0))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: @@ -10095,10 +10095,10 @@ snapshots: '@eslint/core': 1.2.1 levn: 0.4.1 - '@flags-sdk/vercel@1.4.2(@aws-sdk/credential-provider-web-identity@3.972.55)(flags@4.2.0(@opentelemetry/api@1.9.1)(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(ws@8.21.0)': + '@flags-sdk/vercel@1.4.2(@aws-sdk/credential-provider-web-identity@3.972.55)(flags@4.2.0(@opentelemetry/api@1.9.1)(next@16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(next@16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(ws@8.21.0)': dependencies: - '@vercel/flags-core': 1.5.1(@aws-sdk/credential-provider-web-identity@3.972.55)(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(ws@8.21.0) - flags: 4.2.0(@opentelemetry/api@1.9.1)(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@vercel/flags-core': 1.5.1(@aws-sdk/credential-provider-web-identity@3.972.55)(next@16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(ws@8.21.0) + flags: 4.2.0(@opentelemetry/api@1.9.1)(next@16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) transitivePeerDependencies: - '@aws-sdk/credential-provider-web-identity' - '@openfeature/server-sdk' @@ -10411,59 +10411,59 @@ snapshots: '@next/env@16.2.6': {} - '@next/env@16.3.0-canary.68': {} + '@next/env@16.3.0-preview.5': {} '@next/swc-darwin-arm64@16.2.6': optional: true - '@next/swc-darwin-arm64@16.3.0-canary.68': + '@next/swc-darwin-arm64@16.3.0-preview.5': optional: true '@next/swc-darwin-x64@16.2.6': optional: true - '@next/swc-darwin-x64@16.3.0-canary.68': + '@next/swc-darwin-x64@16.3.0-preview.5': optional: true '@next/swc-linux-arm64-gnu@16.2.6': optional: true - '@next/swc-linux-arm64-gnu@16.3.0-canary.68': + '@next/swc-linux-arm64-gnu@16.3.0-preview.5': optional: true '@next/swc-linux-arm64-musl@16.2.6': optional: true - '@next/swc-linux-arm64-musl@16.3.0-canary.68': + '@next/swc-linux-arm64-musl@16.3.0-preview.5': optional: true '@next/swc-linux-x64-gnu@16.2.6': optional: true - '@next/swc-linux-x64-gnu@16.3.0-canary.68': + '@next/swc-linux-x64-gnu@16.3.0-preview.5': optional: true '@next/swc-linux-x64-musl@16.2.6': optional: true - '@next/swc-linux-x64-musl@16.3.0-canary.68': + '@next/swc-linux-x64-musl@16.3.0-preview.5': optional: true '@next/swc-win32-arm64-msvc@16.2.6': optional: true - '@next/swc-win32-arm64-msvc@16.3.0-canary.68': + '@next/swc-win32-arm64-msvc@16.3.0-preview.5': optional: true '@next/swc-win32-x64-msvc@16.2.6': optional: true - '@next/swc-win32-x64-msvc@16.3.0-canary.68': + '@next/swc-win32-x64-msvc@16.3.0-preview.5': optional: true - '@next/third-parties@16.2.9(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3)': + '@next/third-parties@16.2.9(next@16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3)': dependencies: - next: 16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) + next: 16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) react: 19.2.3 third-party-capital: 1.0.20 @@ -13715,9 +13715,9 @@ snapshots: '@use-gesture/core': 10.3.1 react: 19.2.7 - '@vercel/analytics@2.0.1(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3)': + '@vercel/analytics@2.0.1(next@16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3)': optionalDependencies: - next: 16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) + next: 16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) react: 19.2.3 '@vercel/blob@2.4.1': @@ -13747,21 +13747,21 @@ snapshots: '@opentelemetry/api': 1.9.1 next: 16.2.6(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0) - '@vercel/edge-config@1.4.3(@opentelemetry/api@1.9.1)(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))': + '@vercel/edge-config@1.4.3(@opentelemetry/api@1.9.1)(next@16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))': dependencies: '@vercel/edge-config-fs': 0.1.0 optionalDependencies: '@opentelemetry/api': 1.9.1 - next: 16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) + next: 16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) - '@vercel/flags-core@1.5.1(@aws-sdk/credential-provider-web-identity@3.972.55)(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(ws@8.21.0)': + '@vercel/flags-core@1.5.1(@aws-sdk/credential-provider-web-identity@3.972.55)(next@16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(ws@8.21.0)': dependencies: '@vercel/functions': 3.7.3(@aws-sdk/credential-provider-web-identity@3.972.55)(ws@8.21.0) '@vercel/oidc': 3.5.0 jose: 5.2.1 js-xxhash: 4.0.0 optionalDependencies: - next: 16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) + next: 16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) transitivePeerDependencies: - '@aws-sdk/credential-provider-web-identity' - ws @@ -13783,7 +13783,7 @@ snapshots: dependencies: '@upstash/redis': 1.38.0 - '@vercel/microfrontends@2.3.6(@vercel/analytics@2.0.1(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3))(@vercel/speed-insights@1.3.1(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3))(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vite@7.3.6(@types/node@20.19.43)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.101.0)(tsx@4.20.5)(yaml@2.9.0))': + '@vercel/microfrontends@2.3.6(@vercel/analytics@2.0.1(next@16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3))(@vercel/speed-insights@1.3.1(next@16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3))(next@16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vite@7.3.6(@types/node@20.19.43)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.101.0)(tsx@4.20.5)(yaml@2.9.0))': dependencies: '@next/env': 16.0.10 '@types/md5': 2.3.6 @@ -13798,9 +13798,9 @@ snapshots: path-to-regexp: 6.3.0 semver: 7.8.5 optionalDependencies: - '@vercel/analytics': 2.0.1(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3) - '@vercel/speed-insights': 1.3.1(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3) - next: 16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) + '@vercel/analytics': 2.0.1(next@16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3) + '@vercel/speed-insights': 1.3.1(next@16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3) + next: 16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) vite: 7.3.6(@types/node@20.19.43)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.101.0)(tsx@4.20.5)(yaml@2.9.0) @@ -13822,22 +13822,22 @@ snapshots: '@vercel/cli-exec': 1.0.0 jose: 5.10.0 - '@vercel/speed-insights@1.3.1(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3)': + '@vercel/speed-insights@1.3.1(next@16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3)': optionalDependencies: - next: 16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) + next: 16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) react: 19.2.3 - '@vercel/toolbar@0.2.6(@vercel/analytics@2.0.1(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3))(@vercel/speed-insights@1.3.1(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3))(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vite@7.3.6(@types/node@20.19.43)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.101.0)(tsx@4.20.5)(yaml@2.9.0))': + '@vercel/toolbar@0.2.6(@vercel/analytics@2.0.1(next@16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3))(@vercel/speed-insights@1.3.1(next@16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3))(next@16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vite@7.3.6(@types/node@20.19.43)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.101.0)(tsx@4.20.5)(yaml@2.9.0))': dependencies: '@tinyhttp/app': 1.3.0 - '@vercel/microfrontends': 2.3.6(@vercel/analytics@2.0.1(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3))(@vercel/speed-insights@1.3.1(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3))(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vite@7.3.6(@types/node@20.19.43)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.101.0)(tsx@4.20.5)(yaml@2.9.0)) + '@vercel/microfrontends': 2.3.6(@vercel/analytics@2.0.1(next@16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3))(@vercel/speed-insights@1.3.1(next@16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3))(next@16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vite@7.3.6(@types/node@20.19.43)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.101.0)(tsx@4.20.5)(yaml@2.9.0)) chokidar: 3.6.0 execa: 5.1.1 find-up: 5.0.0 get-port: 5.1.1 strip-ansi: 6.0.1 optionalDependencies: - next: 16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) + next: 16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) react: 19.2.3 vite: 7.3.6(@types/node@20.19.43)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.101.0)(tsx@4.20.5)(yaml@2.9.0) transitivePeerDependencies: @@ -14035,7 +14035,7 @@ snapshots: baseline-browser-mapping@2.10.38: {} - better-auth@1.6.20(@opentelemetry/api@1.9.1)(@prisma/client@7.8.0(@typescript/typescript6@6.0.1)(prisma@7.8.0(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(@typescript/typescript6@6.0.1)(magicast@0.5.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)))(mysql2@3.15.3)(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(pg@8.22.0)(prisma@7.8.0(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(@typescript/typescript6@6.0.1)(magicast@0.5.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vitest@3.2.6(@types/debug@4.1.13)(@types/node@20.19.43)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.101.0)(tsx@4.20.5)(yaml@2.9.0)): + better-auth@1.6.20(@opentelemetry/api@1.9.1)(@prisma/client@7.8.0(@typescript/typescript6@6.0.1)(prisma@7.8.0(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(@typescript/typescript6@6.0.1)(magicast@0.5.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)))(mysql2@3.15.3)(next@16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(pg@8.22.0)(prisma@7.8.0(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(@typescript/typescript6@6.0.1)(magicast@0.5.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vitest@3.2.6(@types/debug@4.1.13)(@types/node@20.19.43)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.101.0)(tsx@4.20.5)(yaml@2.9.0)): dependencies: '@better-auth/core': 1.6.20(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0) '@better-auth/drizzle-adapter': 1.6.20(@better-auth/core@1.6.20(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.6(zod@4.4.3))(jose@6.2.3)(kysely@0.29.2)(nanostores@1.3.0))(@better-auth/utils@0.4.2) @@ -14057,7 +14057,7 @@ snapshots: optionalDependencies: '@prisma/client': 7.8.0(@typescript/typescript6@6.0.1)(prisma@7.8.0(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(@typescript/typescript6@6.0.1)(magicast@0.5.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)) mysql2: 3.15.3 - next: 16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) + next: 16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) pg: 8.22.0 prisma: 7.8.0(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(@typescript/typescript6@6.0.1)(magicast@0.5.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) react: 19.2.3 @@ -15028,13 +15028,13 @@ snapshots: locate-path: 6.0.0 path-exists: 4.0.0 - flags@4.2.0(@opentelemetry/api@1.9.1)(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + flags@4.2.0(@opentelemetry/api@1.9.1)(next@16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: '@edge-runtime/cookies': 5.0.2 jose: 5.10.0 optionalDependencies: '@opentelemetry/api': 1.9.1 - next: 16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) + next: 16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) @@ -15176,9 +15176,9 @@ snapshots: fuse.js@7.4.2: {} - geist@1.7.2(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0)): + geist@1.7.2(next@16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0)): dependencies: - next: 16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) + next: 16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) generate-function@2.3.1: dependencies: @@ -16404,10 +16404,10 @@ snapshots: negotiator@1.0.0: {} - next-axiom@1.10.0(@aws-sdk/credential-provider-web-identity@3.972.55)(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3): + next-axiom@1.10.0(@aws-sdk/credential-provider-web-identity@3.972.55)(next@16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3): dependencies: '@vercel/functions': 2.2.13(@aws-sdk/credential-provider-web-identity@3.972.55) - next: 16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) + next: 16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) react: 19.2.3 use-deep-compare: 1.3.0(react@19.2.3) whatwg-fetch: 3.6.20 @@ -16416,14 +16416,14 @@ snapshots: next-intl-swc-plugin-extractor@4.13.0: {} - next-intl@4.13.0(@swc/helpers@0.5.17)(@typescript/typescript6@6.0.1)(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3): + next-intl@4.13.0(@swc/helpers@0.5.17)(@typescript/typescript6@6.0.1)(next@16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3): dependencies: '@formatjs/intl-localematcher': 0.8.10 '@parcel/watcher': 2.5.1 '@swc/core': 1.15.43(@swc/helpers@0.5.17) icu-minify: 4.13.0 negotiator: 1.0.0 - next: 16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) + next: 16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) next-intl-swc-plugin-extractor: 4.13.0 po-parser: 2.1.1 react: 19.2.3 @@ -16438,9 +16438,9 @@ snapshots: next: 16.2.6(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0) react: 19.2.7 - next-seo@7.2.0(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3): + next-seo@7.2.0(next@16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3): dependencies: - next: 16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) + next: 16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) react: 19.2.3 next-themes@0.4.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3): @@ -16507,9 +16507,9 @@ snapshots: - '@babel/core' - babel-plugin-macros - next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0): + next@16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0): dependencies: - '@next/env': 16.3.0-canary.68 + '@next/env': 16.3.0-preview.5 '@swc/helpers': 0.5.15 baseline-browser-mapping: 2.10.38 caniuse-lite: 1.0.30001799 @@ -16518,14 +16518,14 @@ snapshots: react-dom: 19.2.3(react@19.2.3) styled-jsx: 5.1.6(@babel/core@7.29.7)(react@19.2.3) optionalDependencies: - '@next/swc-darwin-arm64': 16.3.0-canary.68 - '@next/swc-darwin-x64': 16.3.0-canary.68 - '@next/swc-linux-arm64-gnu': 16.3.0-canary.68 - '@next/swc-linux-arm64-musl': 16.3.0-canary.68 - '@next/swc-linux-x64-gnu': 16.3.0-canary.68 - '@next/swc-linux-x64-musl': 16.3.0-canary.68 - '@next/swc-win32-arm64-msvc': 16.3.0-canary.68 - '@next/swc-win32-x64-msvc': 16.3.0-canary.68 + '@next/swc-darwin-arm64': 16.3.0-preview.5 + '@next/swc-darwin-x64': 16.3.0-preview.5 + '@next/swc-linux-arm64-gnu': 16.3.0-preview.5 + '@next/swc-linux-arm64-musl': 16.3.0-preview.5 + '@next/swc-linux-x64-gnu': 16.3.0-preview.5 + '@next/swc-linux-x64-musl': 16.3.0-preview.5 + '@next/swc-win32-arm64-msvc': 16.3.0-preview.5 + '@next/swc-win32-x64-msvc': 16.3.0-preview.5 '@opentelemetry/api': 1.9.1 babel-plugin-react-compiler: 1.0.0 sass: 1.101.0 @@ -16534,13 +16534,13 @@ snapshots: - '@babel/core' - babel-plugin-macros - nextstepjs@2.2.0(motion@12.42.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + nextstepjs@2.2.0(motion@12.42.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(next@16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: motion: 12.42.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) optionalDependencies: - next: 16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) + next: 16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) node-addon-api@7.1.1: {} @@ -16563,12 +16563,12 @@ snapshots: dependencies: boolbase: 1.0.0 - nuqs@2.8.9(next@16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3): + nuqs@2.8.9(next@16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0))(react@19.2.3): dependencies: '@standard-schema/spec': 1.0.0 react: 19.2.3 optionalDependencies: - next: 16.3.0-canary.68(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) + next: 16.3.0-preview.5(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.101.0) nypm@0.6.6: dependencies: From 5ddb127aadeef493ffe2dc006dc42bbb182533a9 Mon Sep 17 00:00:00 2001 From: Lucas Doell Date: Fri, 26 Jun 2026 04:05:04 -0400 Subject: [PATCH 14/27] Cache immutable per-map reads aggressively A map's stats never change once uploaded, so wrap getMostPlayedHeroes and getMatchStory in use cache with cacheLife('max') tagged map:${mapId}, used by the map and player pages. Invalidate that tag from the remove-map and set-winner routes so the cache clears only when that specific map changes. --- .../scrim/[scrimId]/map/[mapId]/page.tsx | 18 ++++------ .../map/[mapId]/player/[playerId]/page.tsx | 6 ++-- .../api/scrim/map/[mapId]/set-winner/route.ts | 3 ++ .../web/src/app/api/scrim/remove-map/route.ts | 3 ++ apps/web/src/data/cached/map-cache.ts | 34 +++++++++++++++++++ 5 files changed, 48 insertions(+), 16 deletions(-) create mode 100644 apps/web/src/data/cached/map-cache.ts diff --git a/apps/web/src/app/[team]/scrim/[scrimId]/map/[mapId]/page.tsx b/apps/web/src/app/[team]/scrim/[scrimId]/map/[mapId]/page.tsx index 19d8828a1..2470f2a02 100644 --- a/apps/web/src/app/[team]/scrim/[scrimId]/map/[mapId]/page.tsx +++ b/apps/web/src/app/[team]/scrim/[scrimId]/map/[mapId]/page.tsx @@ -7,8 +7,10 @@ import { MapTabsSkeleton } from "@/components/map/map-tabs-skeleton"; import { PlayerSwitcher } from "@/components/map/player-switcher"; import { ReplayCode } from "@/components/scrim/replay-code"; import { StatsViewBeacon } from "@/components/usage/stats-view-beacon"; -import { MatchStoryService } from "@/data/map/match-story-service"; -import { PlayerService } from "@/data/player"; +import { + getCachedMatchStory, + getCachedMostPlayedHeroes, +} from "@/data/cached/map-cache"; import { AppRuntime } from "@/data/runtime"; import { UserService } from "@/data/user"; import { defaultLocale } from "@/i18n/config"; @@ -105,9 +107,7 @@ export default async function MapDashboardPage( positionalDataEnabled, matchStory, ] = await Promise.all([ - AppRuntime.runPromise( - PlayerService.pipe(Effect.flatMap((svc) => svc.getMostPlayedHeroes(id))) - ), + getCachedMostPlayedHeroes(id), prisma.matchStart.findFirst({ where: { MapDataId: mapDataId }, select: { map_name: true, team_1_name: true }, @@ -132,13 +132,7 @@ export default async function MapDashboardPage( }), tempoChart(), positionalData(), - // A story failure must never break the map page — the tab just hides. - AppRuntime.runPromise( - MatchStoryService.pipe( - Effect.flatMap((svc) => svc.getMatchStory(mapDataId)), - Effect.catchAll(() => Effect.succeed(null)) - ) - ), + getCachedMatchStory(id, mapDataId), ]); const translatedMapName = await translateMapName( diff --git a/apps/web/src/app/[team]/scrim/[scrimId]/map/[mapId]/player/[playerId]/page.tsx b/apps/web/src/app/[team]/scrim/[scrimId]/map/[mapId]/player/[playerId]/page.tsx index 5c283f2eb..96aec7e9f 100644 --- a/apps/web/src/app/[team]/scrim/[scrimId]/map/[mapId]/player/[playerId]/page.tsx +++ b/apps/web/src/app/[team]/scrim/[scrimId]/map/[mapId]/player/[playerId]/page.tsx @@ -7,7 +7,7 @@ import { DefaultOverview } from "@/components/player/default-overview"; import { PlayerTelemetry } from "@/components/player/player-telemetry"; import { Link } from "@/components/ui/link"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; -import { PlayerService } from "@/data/player"; +import { getCachedMostPlayedHeroes } from "@/data/cached/map-cache"; import { Effect } from "effect"; import { AppRuntime } from "@/data/runtime"; import { UserService } from "@/data/user"; @@ -61,9 +61,7 @@ export default async function PlayerDashboardPage( const mapDataId = await resolveMapDataId(id); const playerName = decodeURIComponent(params.playerId); - const mostPlayedHeroes = await AppRuntime.runPromise( - PlayerService.pipe(Effect.flatMap((svc) => svc.getMostPlayedHeroes(id))) - ); + const mostPlayedHeroes = await getCachedMostPlayedHeroes(id); const mapName = await prisma.matchStart.findFirst({ where: { diff --git a/apps/web/src/app/api/scrim/map/[mapId]/set-winner/route.ts b/apps/web/src/app/api/scrim/map/[mapId]/set-winner/route.ts index f6dedc4ea..77d8bdc7f 100644 --- a/apps/web/src/app/api/scrim/map/[mapId]/set-winner/route.ts +++ b/apps/web/src/app/api/scrim/map/[mapId]/set-winner/route.ts @@ -3,6 +3,7 @@ import { auth, canEditScrim, getCurrentUser } from "@/lib/auth"; import { Logger } from "@/lib/logger"; import prisma from "@/lib/prisma"; import { resolveSetWinnerOutcome } from "@/lib/scrim/set-winner-validation"; +import { revalidateTag } from "next/cache"; import { unauthorized, unstable_rethrow } from "next/navigation"; import { after, type NextRequest } from "next/server"; import { z } from "zod"; @@ -70,6 +71,8 @@ export async function POST( data: { winner: parsed.data.winner, winnerSource: "manual" }, }); + revalidateTag(`map:${mapId}`, "max"); + after(async () => { await auditLog.createAuditLog({ userEmail: session.user.email, diff --git a/apps/web/src/app/api/scrim/remove-map/route.ts b/apps/web/src/app/api/scrim/remove-map/route.ts index 6e3226ed3..66648a228 100644 --- a/apps/web/src/app/api/scrim/remove-map/route.ts +++ b/apps/web/src/app/api/scrim/remove-map/route.ts @@ -3,6 +3,7 @@ import { auth, canEditScrim, getCurrentUser } from "@/lib/auth"; import { mapDeletionDuration, mapRemovedCounter } from "@/lib/axiom/metrics"; import { Logger } from "@/lib/logger"; import prisma from "@/lib/prisma"; +import { revalidateTag } from "next/cache"; import { unauthorized } from "next/navigation"; import { after, type NextRequest } from "next/server"; @@ -43,6 +44,8 @@ export async function POST(req: NextRequest) { mapDeletionDuration.record(performance.now() - deleteStart); mapRemovedCounter.add(1); + revalidateTag(`map:${mapId}`, "max"); + after(async () => { await auditLog.createAuditLog({ userEmail: user.email, diff --git a/apps/web/src/data/cached/map-cache.ts b/apps/web/src/data/cached/map-cache.ts new file mode 100644 index 000000000..de7d407b8 --- /dev/null +++ b/apps/web/src/data/cached/map-cache.ts @@ -0,0 +1,34 @@ +import "server-only"; + +import { MatchStoryService } from "@/data/map/match-story-service"; +import { PlayerService } from "@/data/player"; +import { AppRuntime } from "@/data/runtime"; +import { Effect } from "effect"; +import { cacheLife, cacheTag } from "next/cache"; + +// A map's stats are immutable once it's uploaded — adding more maps to the +// scrim never changes an existing map's data. Cache the per-map reads +// indefinitely and invalidate `map:${mapId}` only when that map is removed or +// its winner changes (see the scrim mutation routes). + +export function getCachedMostPlayedHeroes(mapId: number) { + "use cache"; + cacheLife("max"); + cacheTag(`map:${mapId}`); + return AppRuntime.runPromise( + PlayerService.pipe(Effect.flatMap((svc) => svc.getMostPlayedHeroes(mapId))) + ); +} + +export function getCachedMatchStory(mapId: number, mapDataId: number) { + "use cache"; + cacheLife("max"); + cacheTag(`map:${mapId}`); + // A story failure must never break the map page — the tab just hides. + return AppRuntime.runPromise( + MatchStoryService.pipe( + Effect.flatMap((svc) => svc.getMatchStory(mapDataId)), + Effect.catchAll(() => Effect.succeed(null)) + ) + ); +} From b095d86eb674607a3daa2276e45689fec2660d91 Mon Sep 17 00:00:00 2001 From: Lucas Doell Date: Fri, 26 Jun 2026 04:07:19 -0400 Subject: [PATCH 15/27] Make the cached map readers async "use cache" functions must be async; the plain-function form threw at runtime (build-only checks missed it). Verified the map and player pages render with the cache layer. --- apps/web/src/data/cached/map-cache.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/web/src/data/cached/map-cache.ts b/apps/web/src/data/cached/map-cache.ts index de7d407b8..5a045baf8 100644 --- a/apps/web/src/data/cached/map-cache.ts +++ b/apps/web/src/data/cached/map-cache.ts @@ -11,7 +11,7 @@ import { cacheLife, cacheTag } from "next/cache"; // indefinitely and invalidate `map:${mapId}` only when that map is removed or // its winner changes (see the scrim mutation routes). -export function getCachedMostPlayedHeroes(mapId: number) { +export async function getCachedMostPlayedHeroes(mapId: number) { "use cache"; cacheLife("max"); cacheTag(`map:${mapId}`); @@ -20,7 +20,7 @@ export function getCachedMostPlayedHeroes(mapId: number) { ); } -export function getCachedMatchStory(mapId: number, mapDataId: number) { +export async function getCachedMatchStory(mapId: number, mapDataId: number) { "use cache"; cacheLife("max"); cacheTag(`map:${mapId}`); From 23fd2b689aac17318f667b910e709cfb6073470c Mon Sep 17 00:00:00 2001 From: Lucas Doell Date: Fri, 26 Jun 2026 04:26:08 -0400 Subject: [PATCH 16/27] Cache scrim aggregates and team stats with tag invalidation Add scrim-level (scrim:${scrimId}) and team-stats (team-stats:${teamId}) cache readers alongside the per-map ones, and centralize the tags in lib/cache-tags. Wire revalidate calls into every scrim mutation path (create/add-map/remove-map/ remove-scrim/set-winner/update-scrim-options, plus the streaming variants) so a scrim aggregate or a team's stats refresh exactly when a map or scrim changes. --- .../src/app/[team]/scrim/[scrimId]/page.tsx | 42 +++-------- .../src/app/api/scrim/add-map-stream/route.ts | 6 ++ apps/web/src/app/api/scrim/add-map/route.ts | 6 ++ .../api/scrim/create-scrim-stream/route.ts | 4 + .../src/app/api/scrim/create-scrim/route.ts | 4 + .../api/scrim/map/[mapId]/set-winner/route.ts | 17 ++++- .../web/src/app/api/scrim/remove-map/route.ts | 10 ++- .../src/app/api/scrim/remove-scrim/route.ts | 4 + .../api/scrim/update-scrim-options/route.ts | 7 ++ .../app/stats/team/[teamId]/_lib/context.ts | 15 ++-- apps/web/src/data/cached/map-cache.ts | 5 +- apps/web/src/data/cached/scrim-cache.ts | 73 +++++++++++++++++++ apps/web/src/data/cached/team-cache.ts | 35 +++++++++ apps/web/src/lib/cache-tags.ts | 29 ++++++++ 14 files changed, 211 insertions(+), 46 deletions(-) create mode 100644 apps/web/src/data/cached/scrim-cache.ts create mode 100644 apps/web/src/data/cached/team-cache.ts create mode 100644 apps/web/src/lib/cache-tags.ts diff --git a/apps/web/src/app/[team]/scrim/[scrimId]/page.tsx b/apps/web/src/app/[team]/scrim/[scrimId]/page.tsx index 727eb6198..513061566 100644 --- a/apps/web/src/app/[team]/scrim/[scrimId]/page.tsx +++ b/apps/web/src/app/[team]/scrim/[scrimId]/page.tsx @@ -22,12 +22,12 @@ import { TooltipTrigger, } from "@/components/ui/tooltip"; import { - ScrimOverviewService, - ScrimPositionalArtifactsService, - ScrimPositionalStatsService, - ScrimService, -} from "@/data/scrim"; -import { ScrimInitiationService } from "@/data/scrim/initiation-service"; + getCachedScrim, + getCachedScrimInitiation, + getCachedScrimOverview, + getCachedScrimPositionalArtifacts, + getCachedScrimPositionalStats, +} from "@/data/cached/scrim-cache"; import { resolveScrimMapWinners } from "@/data/scrim/map-winner-names"; import { Effect } from "effect"; import { AppRuntime } from "@/data/runtime"; @@ -112,9 +112,7 @@ export default async function ScrimDashboardPage( overviewCardEnabled, showPositional, ] = await Promise.all([ - AppRuntime.runPromise( - ScrimService.pipe(Effect.flatMap((svc) => svc.getScrim(id))) - ), + getCachedScrim(id), prisma.map.findMany({ where: { scrimId: id }, orderBy: [{ order: "asc" }, { id: "asc" }], @@ -228,34 +226,16 @@ export default async function ScrimDashboardPage( const [overviewData, positionalStats, positionalArtifacts, scrimInitiation] = await Promise.all([ overviewCardEnabled && maps.length > 0 && teamId && !isNewTeam - ? AppRuntime.runPromise( - ScrimOverviewService.pipe( - Effect.flatMap((svc) => svc.getScrimOverview(id, teamId)) - ) - ) + ? getCachedScrimOverview(id, teamId) : Promise.resolve(null), showPositional && maps.length > 0 - ? AppRuntime.runPromise( - ScrimPositionalStatsService.pipe( - Effect.flatMap((svc) => svc.getScrimPositionalStats(id)) - ) - ) + ? getCachedScrimPositionalStats(id) : Promise.resolve(null), showPositional && maps.length > 0 && teamId - ? AppRuntime.runPromise( - ScrimPositionalArtifactsService.pipe( - Effect.flatMap((svc) => - svc.getScrimPositionalArtifacts(id, teamId) - ) - ) - ) + ? getCachedScrimPositionalArtifacts(id, teamId) : Promise.resolve(null), overviewCardEnabled && maps.length > 0 && teamId && !isNewTeam - ? AppRuntime.runPromise( - ScrimInitiationService.pipe( - Effect.flatMap((svc) => svc.getScrimInitiation(id)) - ) - ) + ? getCachedScrimInitiation(id) : Promise.resolve(null), ]); diff --git a/apps/web/src/app/api/scrim/add-map-stream/route.ts b/apps/web/src/app/api/scrim/add-map-stream/route.ts index e42fac261..742208936 100644 --- a/apps/web/src/app/api/scrim/add-map-stream/route.ts +++ b/apps/web/src/app/api/scrim/add-map-stream/route.ts @@ -1,6 +1,7 @@ import { auditLog } from "@/lib/audit-logs"; import { auth, canEditScrim, getCurrentUser } from "@/lib/auth"; import { mapAddedCounter, scrimParsingDuration } from "@/lib/axiom/metrics"; +import { revalidateScrim, revalidateTeamStats } from "@/lib/cache-tags"; import { Logger } from "@/lib/logger"; import { createNewMap } from "@/lib/parser"; import prisma from "@/lib/prisma"; @@ -111,6 +112,11 @@ export async function POST(req: NextRequest) { const parseDuration = performance.now() - parseStart; scrimParsingDuration.record(parseDuration); mapAddedCounter.add(1); + + // A new map changes the scrim's aggregates and the team's stats. + revalidateScrim(scrimId); + if (scrim?.teamId) revalidateTeamStats(scrim.teamId); + void usage.track({ name: UsageEventName.SCRIM_MAP_ADD, userId: user?.id, diff --git a/apps/web/src/app/api/scrim/add-map/route.ts b/apps/web/src/app/api/scrim/add-map/route.ts index 0e29b5870..371b4cb18 100644 --- a/apps/web/src/app/api/scrim/add-map/route.ts +++ b/apps/web/src/app/api/scrim/add-map/route.ts @@ -1,6 +1,7 @@ import { auditLog } from "@/lib/audit-logs"; import { auth, canEditScrim, getCurrentUser } from "@/lib/auth"; import { mapAddedCounter, scrimParsingDuration } from "@/lib/axiom/metrics"; +import { revalidateScrim, revalidateTeamStats } from "@/lib/cache-tags"; import { Logger } from "@/lib/logger"; import { createNewMap } from "@/lib/parser"; import prisma from "@/lib/prisma"; @@ -136,6 +137,11 @@ export async function POST(req: NextRequest) { const parseDuration = performance.now() - parseStart; scrimParsingDuration.record(parseDuration); mapAddedCounter.add(1); + + // A new map changes the scrim's aggregates and the team's stats. + revalidateScrim(scrimId); + if (scrim?.teamId) revalidateTeamStats(scrim.teamId); + void usage.track({ name: UsageEventName.SCRIM_MAP_ADD, userId: user?.id, diff --git a/apps/web/src/app/api/scrim/create-scrim-stream/route.ts b/apps/web/src/app/api/scrim/create-scrim-stream/route.ts index b8e8ff7c2..69d1b5598 100644 --- a/apps/web/src/app/api/scrim/create-scrim-stream/route.ts +++ b/apps/web/src/app/api/scrim/create-scrim-stream/route.ts @@ -7,6 +7,7 @@ import { scrimCreatedCounter, scrimParsingDuration, } from "@/lib/axiom/metrics"; +import { revalidateTeamStats } from "@/lib/cache-tags"; import { UsageEventName } from "@/lib/usage/names"; import { usage } from "@/lib/usage/server"; import { sendScrimNotifications } from "@/lib/bot-events"; @@ -156,6 +157,9 @@ export async function POST(request: NextRequest) { } } + // A new scrim for the team changes its aggregated stats. + if (teamId) revalidateTeamStats(teamId); + emit({ type: "done", scrimId: newScrimId }); event.outcome = "success"; event.scrim_id = newScrimId; diff --git a/apps/web/src/app/api/scrim/create-scrim/route.ts b/apps/web/src/app/api/scrim/create-scrim/route.ts index 1d31dd85f..dd732f731 100644 --- a/apps/web/src/app/api/scrim/create-scrim/route.ts +++ b/apps/web/src/app/api/scrim/create-scrim/route.ts @@ -4,6 +4,7 @@ import { UserService } from "@/data/user"; import { auditLog } from "@/lib/audit-logs"; import { auth, canManageTeam } from "@/lib/auth"; import { withRequestContext } from "@/lib/axiom/baggage"; +import { revalidateTeamStats } from "@/lib/cache-tags"; import { rateLimitHitCounter, scrimCreatedCounter, @@ -325,6 +326,9 @@ export async function POST(request: NextRequest) { } }); + // A new scrim for the team changes its aggregated stats. + if (teamId) revalidateTeamStats(teamId); + return Response.json({ scrimId: newScrimId }, { status: 200 }); } ); diff --git a/apps/web/src/app/api/scrim/map/[mapId]/set-winner/route.ts b/apps/web/src/app/api/scrim/map/[mapId]/set-winner/route.ts index 77d8bdc7f..6ef676137 100644 --- a/apps/web/src/app/api/scrim/map/[mapId]/set-winner/route.ts +++ b/apps/web/src/app/api/scrim/map/[mapId]/set-winner/route.ts @@ -1,9 +1,13 @@ import { auditLog } from "@/lib/audit-logs"; import { auth, canEditScrim, getCurrentUser } from "@/lib/auth"; import { Logger } from "@/lib/logger"; +import { + revalidateMap, + revalidateScrim, + revalidateTeamStats, +} from "@/lib/cache-tags"; import prisma from "@/lib/prisma"; import { resolveSetWinnerOutcome } from "@/lib/scrim/set-winner-validation"; -import { revalidateTag } from "next/cache"; import { unauthorized, unstable_rethrow } from "next/navigation"; import { after, type NextRequest } from "next/server"; import { z } from "zod"; @@ -39,7 +43,12 @@ export async function POST( const map = await prisma.map.findUnique({ where: { id: mapId }, - select: { id: true, scrimId: true, mapData: { select: { id: true } } }, + select: { + id: true, + scrimId: true, + mapData: { select: { id: true } }, + Scrim: { select: { teamId: true } }, + }, }); if (!map?.scrimId) { return Response.json({ error: "Map not found" }, { status: 404 }); @@ -71,7 +80,9 @@ export async function POST( data: { winner: parsed.data.winner, winnerSource: "manual" }, }); - revalidateTag(`map:${mapId}`, "max"); + revalidateMap(mapId); + revalidateScrim(map.scrimId); + if (map.Scrim?.teamId) revalidateTeamStats(map.Scrim.teamId); after(async () => { await auditLog.createAuditLog({ diff --git a/apps/web/src/app/api/scrim/remove-map/route.ts b/apps/web/src/app/api/scrim/remove-map/route.ts index 66648a228..f7615ceec 100644 --- a/apps/web/src/app/api/scrim/remove-map/route.ts +++ b/apps/web/src/app/api/scrim/remove-map/route.ts @@ -2,8 +2,12 @@ import { auditLog } from "@/lib/audit-logs"; import { auth, canEditScrim, getCurrentUser } from "@/lib/auth"; import { mapDeletionDuration, mapRemovedCounter } from "@/lib/axiom/metrics"; import { Logger } from "@/lib/logger"; +import { + revalidateMap, + revalidateScrim, + revalidateTeamStats, +} from "@/lib/cache-tags"; import prisma from "@/lib/prisma"; -import { revalidateTag } from "next/cache"; import { unauthorized } from "next/navigation"; import { after, type NextRequest } from "next/server"; @@ -44,7 +48,9 @@ export async function POST(req: NextRequest) { mapDeletionDuration.record(performance.now() - deleteStart); mapRemovedCounter.add(1); - revalidateTag(`map:${mapId}`, "max"); + revalidateMap(mapId); + revalidateScrim(scrim.id); + if (scrim.teamId) revalidateTeamStats(scrim.teamId); after(async () => { await auditLog.createAuditLog({ diff --git a/apps/web/src/app/api/scrim/remove-scrim/route.ts b/apps/web/src/app/api/scrim/remove-scrim/route.ts index f5b68e8de..cd5e4af79 100644 --- a/apps/web/src/app/api/scrim/remove-scrim/route.ts +++ b/apps/web/src/app/api/scrim/remove-scrim/route.ts @@ -1,6 +1,7 @@ import { auditLog } from "@/lib/audit-logs"; import { auth, canEditScrim, getCurrentUser } from "@/lib/auth"; import { Logger } from "@/lib/logger"; +import { revalidateScrim, revalidateTeamStats } from "@/lib/cache-tags"; import { notifications } from "@/lib/notifications"; import prisma from "@/lib/prisma"; import { unauthorized } from "next/navigation"; @@ -53,6 +54,9 @@ export async function POST(req: NextRequest) { await prisma.map.deleteMany({ where: { scrimId } }); await prisma.scrim.delete({ where: { id: scrimId } }); + revalidateScrim(scrimId); + if (scrim.teamId) revalidateTeamStats(scrim.teamId); + after(async () => { await auditLog.createAuditLog({ userEmail: user.email, diff --git a/apps/web/src/app/api/scrim/update-scrim-options/route.ts b/apps/web/src/app/api/scrim/update-scrim-options/route.ts index 5c1468308..80254fbc0 100644 --- a/apps/web/src/app/api/scrim/update-scrim-options/route.ts +++ b/apps/web/src/app/api/scrim/update-scrim-options/route.ts @@ -1,5 +1,6 @@ import { auditLog } from "@/lib/audit-logs"; import { auth, canEditScrim, canManageTeam, getCurrentUser } from "@/lib/auth"; +import { revalidateScrim, revalidateTeamStats } from "@/lib/cache-tags"; import prisma from "@/lib/prisma"; import { unauthorized } from "next/navigation"; import { after, type NextRequest } from "next/server"; @@ -115,6 +116,12 @@ export async function POST(req: NextRequest) { } }); + revalidateScrim(scrim.id); + // The scrim may have moved teams, so refresh both the old and new team's stats. + const newTeamId = targetTeamId === 0 ? null : targetTeamId; + if (scrim.teamId) revalidateTeamStats(scrim.teamId); + if (newTeamId && newTeamId !== scrim.teamId) revalidateTeamStats(newTeamId); + after(async () => { await auditLog.createAuditLog({ userEmail: user.email, diff --git a/apps/web/src/app/stats/team/[teamId]/_lib/context.ts b/apps/web/src/app/stats/team/[teamId]/_lib/context.ts index 5c0770c6f..3cbc9564d 100644 --- a/apps/web/src/app/stats/team/[teamId]/_lib/context.ts +++ b/apps/web/src/app/stats/team/[teamId]/_lib/context.ts @@ -1,6 +1,9 @@ +import { + getCachedTeamSubstituteNames, + getCachedTeamWinrates, +} from "@/data/cached/team-cache"; import { AppRuntime } from "@/data/runtime"; -import { type TeamDateRange, TeamStatsService } from "@/data/team"; -import { getTeamSubstituteNames } from "@/data/team/substitutes"; +import { type TeamDateRange } from "@/data/team"; import { UserService } from "@/data/user"; import { $Enums } from "@/generated/prisma/browser"; import { auth, canManageTeam } from "@/lib/auth"; @@ -124,7 +127,7 @@ export async function loadTeamStatsShell( const [isManager, substituteNames] = await Promise.all([ canManageTeam(teamId, user), - getTeamSubstituteNames(teamId), + getCachedTeamSubstituteNames(teamId), ]); const [timeframe1, timeframe2, timeframe3] = await Promise.all([ @@ -181,11 +184,7 @@ export async function loadTeamStatsHeaderData( ) { const { teamId, team, dateRange, substituteNames } = shell; const [winrates, teamTsr] = await Promise.all([ - AppRuntime.runPromise( - TeamStatsService.pipe( - Effect.flatMap((svc) => svc.getTeamWinrates(teamId, dateRange)) - ) - ), + getCachedTeamWinrates(teamId, dateRange), prisma.scrim .findMany({ where: { teamId }, select: { id: true } }) .then((rows) => diff --git a/apps/web/src/data/cached/map-cache.ts b/apps/web/src/data/cached/map-cache.ts index 5a045baf8..234816c91 100644 --- a/apps/web/src/data/cached/map-cache.ts +++ b/apps/web/src/data/cached/map-cache.ts @@ -3,6 +3,7 @@ import "server-only"; import { MatchStoryService } from "@/data/map/match-story-service"; import { PlayerService } from "@/data/player"; import { AppRuntime } from "@/data/runtime"; +import { mapTag } from "@/lib/cache-tags"; import { Effect } from "effect"; import { cacheLife, cacheTag } from "next/cache"; @@ -14,7 +15,7 @@ import { cacheLife, cacheTag } from "next/cache"; export async function getCachedMostPlayedHeroes(mapId: number) { "use cache"; cacheLife("max"); - cacheTag(`map:${mapId}`); + cacheTag(mapTag(mapId)); return AppRuntime.runPromise( PlayerService.pipe(Effect.flatMap((svc) => svc.getMostPlayedHeroes(mapId))) ); @@ -23,7 +24,7 @@ export async function getCachedMostPlayedHeroes(mapId: number) { export async function getCachedMatchStory(mapId: number, mapDataId: number) { "use cache"; cacheLife("max"); - cacheTag(`map:${mapId}`); + cacheTag(mapTag(mapId)); // A story failure must never break the map page — the tab just hides. return AppRuntime.runPromise( MatchStoryService.pipe( diff --git a/apps/web/src/data/cached/scrim-cache.ts b/apps/web/src/data/cached/scrim-cache.ts new file mode 100644 index 000000000..85da9df69 --- /dev/null +++ b/apps/web/src/data/cached/scrim-cache.ts @@ -0,0 +1,73 @@ +import "server-only"; + +import { AppRuntime } from "@/data/runtime"; +import { + ScrimOverviewService, + ScrimPositionalArtifactsService, + ScrimPositionalStatsService, + ScrimService, +} from "@/data/scrim"; +import { ScrimInitiationService } from "@/data/scrim/initiation-service"; +import { scrimTag } from "@/lib/cache-tags"; +import { Effect } from "effect"; +import { cacheLife, cacheTag } from "next/cache"; + +// Scrim-level aggregates depend on the full set of maps in the scrim, so unlike +// per-map reads they change when maps are added, removed, or edited. They're +// tagged `scrim:${scrimId}` and invalidated from the scrim mutation routes. + +export async function getCachedScrim(scrimId: number) { + "use cache"; + cacheLife("max"); + cacheTag(scrimTag(scrimId)); + return AppRuntime.runPromise( + ScrimService.pipe(Effect.flatMap((svc) => svc.getScrim(scrimId))) + ); +} + +export async function getCachedScrimOverview(scrimId: number, teamId: number) { + "use cache"; + cacheLife("max"); + cacheTag(scrimTag(scrimId)); + return AppRuntime.runPromise( + ScrimOverviewService.pipe( + Effect.flatMap((svc) => svc.getScrimOverview(scrimId, teamId)) + ) + ); +} + +export async function getCachedScrimPositionalStats(scrimId: number) { + "use cache"; + cacheLife("max"); + cacheTag(scrimTag(scrimId)); + return AppRuntime.runPromise( + ScrimPositionalStatsService.pipe( + Effect.flatMap((svc) => svc.getScrimPositionalStats(scrimId)) + ) + ); +} + +export async function getCachedScrimPositionalArtifacts( + scrimId: number, + teamId: number +) { + "use cache"; + cacheLife("max"); + cacheTag(scrimTag(scrimId)); + return AppRuntime.runPromise( + ScrimPositionalArtifactsService.pipe( + Effect.flatMap((svc) => svc.getScrimPositionalArtifacts(scrimId, teamId)) + ) + ); +} + +export async function getCachedScrimInitiation(scrimId: number) { + "use cache"; + cacheLife("max"); + cacheTag(scrimTag(scrimId)); + return AppRuntime.runPromise( + ScrimInitiationService.pipe( + Effect.flatMap((svc) => svc.getScrimInitiation(scrimId)) + ) + ); +} diff --git a/apps/web/src/data/cached/team-cache.ts b/apps/web/src/data/cached/team-cache.ts new file mode 100644 index 000000000..1d8c171a7 --- /dev/null +++ b/apps/web/src/data/cached/team-cache.ts @@ -0,0 +1,35 @@ +import "server-only"; + +import { AppRuntime } from "@/data/runtime"; +import { type TeamDateRange, TeamStatsService } from "@/data/team"; +import { getTeamSubstituteNames } from "@/data/team/substitutes"; +import { teamStatsTag } from "@/lib/cache-tags"; +import { Effect } from "effect"; +import { cacheLife, cacheTag } from "next/cache"; + +// Team stats change whenever a scrim is uploaded to (or removed from) the team, +// so they're tagged `team-stats:${teamId}` and invalidated from the scrim +// mutation routes. `cacheLife('days')` is a safety net in case an invalidation +// is ever missed; the per-request date range is passed in as part of the key, +// never read inside the cache scope. + +export async function getCachedTeamWinrates( + teamId: number, + dateRange: TeamDateRange | undefined +) { + "use cache"; + cacheLife("days"); + cacheTag(teamStatsTag(teamId)); + return AppRuntime.runPromise( + TeamStatsService.pipe( + Effect.flatMap((svc) => svc.getTeamWinrates(teamId, dateRange)) + ) + ); +} + +export async function getCachedTeamSubstituteNames(teamId: number) { + "use cache"; + cacheLife("days"); + cacheTag(teamStatsTag(teamId)); + return getTeamSubstituteNames(teamId); +} diff --git a/apps/web/src/lib/cache-tags.ts b/apps/web/src/lib/cache-tags.ts new file mode 100644 index 000000000..17bcb2626 --- /dev/null +++ b/apps/web/src/lib/cache-tags.ts @@ -0,0 +1,29 @@ +import { revalidateTag } from "next/cache"; + +// Tag helpers for the cached data readers under `src/data/cached`. Kept in one +// place so the tag format stays in sync between the readers and the mutation +// routes that invalidate them. + +export function scrimTag(scrimId: number) { + return `scrim:${scrimId}`; +} + +export function mapTag(mapId: number) { + return `map:${mapId}`; +} + +export function teamStatsTag(teamId: number) { + return `team-stats:${teamId}`; +} + +export function revalidateScrim(scrimId: number) { + revalidateTag(scrimTag(scrimId), "max"); +} + +export function revalidateMap(mapId: number) { + revalidateTag(mapTag(mapId), "max"); +} + +export function revalidateTeamStats(teamId: number) { + revalidateTag(teamStatsTag(teamId), "max"); +} From d0a696437f064be477c0891fb7dd8b8f7a40bf99 Mon Sep 17 00:00:00 2001 From: Lucas Doell Date: Fri, 26 Jun 2026 04:27:42 -0400 Subject: [PATCH 17/27] Drop redundant comments on cache invalidation calls --- apps/web/src/app/api/scrim/add-map-stream/route.ts | 1 - apps/web/src/app/api/scrim/add-map/route.ts | 1 - apps/web/src/app/api/scrim/create-scrim-stream/route.ts | 1 - apps/web/src/app/api/scrim/create-scrim/route.ts | 1 - 4 files changed, 4 deletions(-) diff --git a/apps/web/src/app/api/scrim/add-map-stream/route.ts b/apps/web/src/app/api/scrim/add-map-stream/route.ts index 742208936..d76032158 100644 --- a/apps/web/src/app/api/scrim/add-map-stream/route.ts +++ b/apps/web/src/app/api/scrim/add-map-stream/route.ts @@ -113,7 +113,6 @@ export async function POST(req: NextRequest) { scrimParsingDuration.record(parseDuration); mapAddedCounter.add(1); - // A new map changes the scrim's aggregates and the team's stats. revalidateScrim(scrimId); if (scrim?.teamId) revalidateTeamStats(scrim.teamId); diff --git a/apps/web/src/app/api/scrim/add-map/route.ts b/apps/web/src/app/api/scrim/add-map/route.ts index 371b4cb18..9b3abb5d5 100644 --- a/apps/web/src/app/api/scrim/add-map/route.ts +++ b/apps/web/src/app/api/scrim/add-map/route.ts @@ -138,7 +138,6 @@ export async function POST(req: NextRequest) { scrimParsingDuration.record(parseDuration); mapAddedCounter.add(1); - // A new map changes the scrim's aggregates and the team's stats. revalidateScrim(scrimId); if (scrim?.teamId) revalidateTeamStats(scrim.teamId); diff --git a/apps/web/src/app/api/scrim/create-scrim-stream/route.ts b/apps/web/src/app/api/scrim/create-scrim-stream/route.ts index 69d1b5598..2c827af57 100644 --- a/apps/web/src/app/api/scrim/create-scrim-stream/route.ts +++ b/apps/web/src/app/api/scrim/create-scrim-stream/route.ts @@ -157,7 +157,6 @@ export async function POST(request: NextRequest) { } } - // A new scrim for the team changes its aggregated stats. if (teamId) revalidateTeamStats(teamId); emit({ type: "done", scrimId: newScrimId }); diff --git a/apps/web/src/app/api/scrim/create-scrim/route.ts b/apps/web/src/app/api/scrim/create-scrim/route.ts index dd732f731..eb8e2e785 100644 --- a/apps/web/src/app/api/scrim/create-scrim/route.ts +++ b/apps/web/src/app/api/scrim/create-scrim/route.ts @@ -326,7 +326,6 @@ export async function POST(request: NextRequest) { } }); - // A new scrim for the team changes its aggregated stats. if (teamId) revalidateTeamStats(teamId); return Response.json({ scrimId: newScrimId }, { status: 200 }); From b9a36831e8e39648e48c3f89a5f93e8fd0d9cb88 Mon Sep 17 00:00:00 2001 From: Lucas Doell Date: Fri, 26 Jun 2026 09:15:14 -0400 Subject: [PATCH 18/27] Use BatchSpanProcessor so telemetry never blocks prerendering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SimpleSpanProcessor exports each span synchronously on end, reading Date.now() inside the render — which Cache Components rejects during the per-request shell prerenders the dev server (and partialPrefetching) run. BatchSpanProcessor defers export to a timer, off the render path, in every environment. --- apps/web/src/instrumentation-node.ts | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/apps/web/src/instrumentation-node.ts b/apps/web/src/instrumentation-node.ts index c0cf75290..e1d9db8c2 100644 --- a/apps/web/src/instrumentation-node.ts +++ b/apps/web/src/instrumentation-node.ts @@ -15,7 +15,6 @@ import { import { BatchSpanProcessor, NodeTracerProvider, - SimpleSpanProcessor, } from "@opentelemetry/sdk-trace-node"; import { ATTR_SERVICE_NAME, @@ -74,11 +73,10 @@ export function registerNode() { const provider = new NodeTracerProvider({ resource: RESOURCE, - spanProcessors: [ - IS_PROD - ? new BatchSpanProcessor(otlpTraceExporter) - : new SimpleSpanProcessor(otlpTraceExporter), - ], + // BatchSpanProcessor exports on a timer rather than synchronously on span + // end, so trace export never reads Date.now() inside a Cache Components + // prerender pass (which the dev server runs per request). + spanProcessors: [new BatchSpanProcessor(otlpTraceExporter)], }); registerInstrumentations({ @@ -188,9 +186,7 @@ const EffectTracingLive = NodeSdk.layer(() => ({ [ATTR_DEPLOYMENT_ENVIRONMENT_NAME]: ENVIRONMENT, }, }, - spanProcessor: IS_PROD - ? new BatchSpanProcessor(new OTLPTraceExporter(OTLP_CONFIG)) - : new SimpleSpanProcessor(new OTLPTraceExporter(OTLP_CONFIG)), + spanProcessor: new BatchSpanProcessor(new OTLPTraceExporter(OTLP_CONFIG)), metricReader: new PeriodicExportingMetricReader({ exporter: new OTLPMetricExporter(METRIC_EXPORTER_CONFIG), exportIntervalMillis: 10_000, From 4db1e85794072b840e01dcda4e5191e37fedd312 Mon Sep 17 00:00:00 2001 From: Lucas Doell Date: Fri, 26 Jun 2026 09:22:06 -0400 Subject: [PATCH 19/27] Hold partialPrefetching until authed routes are instant-ready --- apps/web/next.config.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/web/next.config.ts b/apps/web/next.config.ts index 6881b833a..a99c3cde2 100644 --- a/apps/web/next.config.ts +++ b/apps/web/next.config.ts @@ -26,7 +26,10 @@ const cspHeader = ` const nextConfig: NextConfig = { cacheComponents: true, - partialPrefetching: true, + // partialPrefetching (instant navigations) is held until the authed routes + // are instant-ready — today their layouts read auth() at the top, so the + // per-route shell can't prerender and every authed nav reports as blocking. + // Re-enable once those shells stream auth-gated content behind . reactCompiler: true, typedRoutes: true, transpilePackages: ["@parsertime/transactional"], From 8889845ec3900760c35dbe4da971555bde04e7c4 Mon Sep 17 00:00:00 2001 From: Lucas Doell Date: Fri, 26 Jun 2026 09:32:47 -0400 Subject: [PATCH 20/27] Stream auth-gated content on the scrim and map layouts Render the static chrome immediately and move the authorization check (and the Footer, which reads the locale cookie + current year) into a Suspense boundary, so navigating to a scrim or map paints instantly instead of blocking. Clears the 16.3 Instant Insight for those routes and re-enables partialPrefetching. --- apps/web/next.config.ts | 5 +-- .../src/app/[team]/scrim/[scrimId]/layout.tsx | 29 +++++++++------ .../scrim/[scrimId]/map/[mapId]/layout.tsx | 37 +++++++++++-------- 3 files changed, 40 insertions(+), 31 deletions(-) diff --git a/apps/web/next.config.ts b/apps/web/next.config.ts index a99c3cde2..6881b833a 100644 --- a/apps/web/next.config.ts +++ b/apps/web/next.config.ts @@ -26,10 +26,7 @@ const cspHeader = ` const nextConfig: NextConfig = { cacheComponents: true, - // partialPrefetching (instant navigations) is held until the authed routes - // are instant-ready — today their layouts read auth() at the top, so the - // per-route shell can't prerender and every authed nav reports as blocking. - // Re-enable once those shells stream auth-gated content behind . + partialPrefetching: true, reactCompiler: true, typedRoutes: true, transpilePackages: ["@parsertime/transactional"], diff --git a/apps/web/src/app/[team]/scrim/[scrimId]/layout.tsx b/apps/web/src/app/[team]/scrim/[scrimId]/layout.tsx index b89711136..c6e4981dc 100644 --- a/apps/web/src/app/[team]/scrim/[scrimId]/layout.tsx +++ b/apps/web/src/app/[team]/scrim/[scrimId]/layout.tsx @@ -1,20 +1,27 @@ import { NoAuthCard } from "@/components/auth/no-auth"; import { SelectedPlayerProvider } from "@/components/map/player-switcher"; import { isAuthedToViewScrim } from "@/lib/auth"; +import { Suspense, type ReactNode } from "react"; -export default async function ScrimDashboardLayout( +export default function ScrimDashboardLayout( props: LayoutProps<"/[team]/scrim/[scrimId]"> ) { - const params = await props.params; - - const { children } = props; - - const id = parseInt(params.scrimId); - const isAuthed = await isAuthedToViewScrim(id); - - if (!isAuthed) { - return ; - } + return ( + + {props.children} + + ); +} +async function ScrimAuthGate({ + params, + children, +}: { + params: LayoutProps<"/[team]/scrim/[scrimId]">["params"]; + children: ReactNode; +}) { + const { scrimId } = await params; + const isAuthed = await isAuthedToViewScrim(parseInt(scrimId)); + if (!isAuthed) return ; return {children}; } diff --git a/apps/web/src/app/[team]/scrim/[scrimId]/map/[mapId]/layout.tsx b/apps/web/src/app/[team]/scrim/[scrimId]/map/[mapId]/layout.tsx index 93599c5f2..924b6f811 100644 --- a/apps/web/src/app/[team]/scrim/[scrimId]/map/[mapId]/layout.tsx +++ b/apps/web/src/app/[team]/scrim/[scrimId]/map/[mapId]/layout.tsx @@ -3,28 +3,33 @@ import { Footer } from "@/components/footer"; import { MobileBanner } from "@/components/map/mobile-banner"; import { SelectedPlayerProvider } from "@/components/map/player-switcher"; import { isAuthedToViewMap } from "@/lib/auth"; +import { Suspense, type ReactNode } from "react"; -export default async function MapDashboardLayout( +export default function MapDashboardLayout( props: LayoutProps<"/[team]/scrim/[scrimId]/map/[mapId]"> ) { - const params = await props.params; - - const { children } = props; - - const scrimId = parseInt(params.scrimId); - const mapId = parseInt(params.mapId); - - const isAuthed = await isAuthedToViewMap(scrimId, mapId); - - if (!isAuthed) { - return ; - } - + // The chrome (provider, banner, footer) is static so navigations to a map + // paint instantly; the authorization check and the page stream in behind it. return ( - {children} -