diff --git a/next.config.ts b/next.config.ts index 1fbfcb7eb..1ad2006d3 100644 --- a/next.config.ts +++ b/next.config.ts @@ -4,6 +4,39 @@ import bundleAnalyzer from "@next/bundle-analyzer"; const nextConfig: NextConfig = { output: "standalone", poweredByHeader: false, + // v1.4.33 IW2 — strip `console.*` calls from the production bundle + // (keep `console.error` + `console.warn` so the GlitchTip reporter + // and prod-side debug rails still surface). The Lighthouse audit + // flagged ~211 KiB of bundled JS as "unminified" — Turbopack already + // mangles + minifies the chunks, but the in-tree `console.log` + // breadcrumbs from the chart wiring + Coach SSE handlers carried + // hundreds of preserved string literals through to the client. The + // SWC compiler drops the calls + their literal-only arguments + // entirely in production. + compiler: { + removeConsole: + process.env.NODE_ENV === "production" + ? { exclude: ["error", "warn"] } + : false, + }, + // v1.4.33 IW2 — bfcache hygiene. `Permissions-Policy: unload=()` + // tells the browser the page does not need the `unload` event, + // which Chromium uses as a hint to admit the page to the + // back/forward cache on navigation away. Pair with the absence of + // any `beforeunload` / `unload` listener in our own code so the + // bfcache restore path stays clear. The other CSP-style security + // headers already live on the response via the standalone runtime; + // we add only the bfcache hint here. + async headers() { + return [ + { + source: "/:path*", + headers: [ + { key: "Permissions-Policy", value: "unload=()" }, + ], + }, + ]; + }, serverExternalPackages: [ "@prisma/client", "pg-boss", diff --git a/src/app/insights/page.tsx b/src/app/insights/page.tsx index 8fdfffb72..0cfc94edb 100644 --- a/src/app/insights/page.tsx +++ b/src/app/insights/page.tsx @@ -1,6 +1,7 @@ "use client"; import { useQuery } from "@tanstack/react-query"; +import dynamic from "next/dynamic"; import Link from "next/link"; import { Loader2, TrendingUp } from "lucide-react"; @@ -11,15 +12,60 @@ import { useTranslations } from "@/lib/i18n/context"; import { Button } from "@/components/ui/button"; import { EmptyState } from "@/components/ui/empty-state"; import { HeroStrip } from "@/components/insights/hero-strip"; -import { DailyBriefing } from "@/components/insights/daily-briefing"; -import { TrendsRow } from "@/components/insights/trends-row"; -import { CorrelationRow } from "@/components/insights/correlation-row"; import { useInsightsAdvisorQuery } from "@/components/insights/use-insights-advisor"; import { useCoachLaunch } from "@/lib/insights/coach-launch-context"; import { useAnalyticsQuery } from "@/lib/queries/use-analytics-query"; import type { CorrelationResult } from "@/lib/insights/correlations"; import type { DataSummary } from "@/lib/analytics/trends"; +/** + * v1.4.33 IW2 — defer the three below-the-fold mother-page blocks + * behind `next/dynamic`. `` (the only above-the-fold piece) + * stays an eager import so the initial paint shows the greeting and + * health-score badge without a flash; the briefing, correlation row + * and trends row each carry their own icon-set + chart wiring (a chart + * card alone weighs in at the lucide tree-shake limit) and used to + * land on every Insights cold mount. Loader skeletons match the + * existing fallback so the layout doesn't shift while the chunks + * resolve. + */ +const DailyBriefing = dynamic( + () => + import("@/components/insights/daily-briefing").then((mod) => ({ + default: mod.DailyBriefing, + })), + { + ssr: false, + loading: () => ( +
+ ), + }, +); +const CorrelationRow = dynamic( + () => + import("@/components/insights/correlation-row").then((mod) => ({ + default: mod.CorrelationRow, + })), + { + ssr: false, + loading: () => ( +
+ ), + }, +); +const TrendsRow = dynamic( + () => + import("@/components/insights/trends-row").then((mod) => ({ + default: mod.TrendsRow, + })), + { + ssr: false, + loading: () => ( +
+ ), + }, +); + /** * v1.4.25 W4d — Insights mother page. *