From bb2b1de742057c722d434abf18f6d56e0f75fbc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sat, 9 May 2026 16:23:32 +0200 Subject: [PATCH] perf(insights): defer Recharts ScatterChart imports via next/dynamic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The insights page imported seven Recharts symbols (ScatterChart, Scatter, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer) eagerly at module top, which pulled the entire ~108 KiB Brotli Recharts chunk onto the initial bundle even though every scatter card sits inside a `length >= 5` gate below the fold. Switching each symbol to next/dynamic with `ssr: false` matches the existing HealthChart / MoodChart pattern in the same file. Recharts is still loaded once via the shared chunk graph the first time any scatter section mounts; until then, /insights ships the same JS payload as chart-free pages. Verified static-only — local pnpm build is blocked by the documented Node-25 turbopack regression. Full deltas captured in docs/audit/v15-performance.md (v1.5 phase-4 perf audit). Co-Authored-By: Claude Opus 4.7 (1M context) --- src/app/insights/page.tsx | 43 +++++++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 9 deletions(-) diff --git a/src/app/insights/page.tsx b/src/app/insights/page.tsx index 979ad9b9c..e64e7294c 100644 --- a/src/app/insights/page.tsx +++ b/src/app/insights/page.tsx @@ -39,15 +39,40 @@ import { TrendingUp, } from "lucide-react"; import { InsightStatusCard } from "@/components/insights/insight-status-card"; -import { - ScatterChart, - Scatter, - XAxis, - YAxis, - CartesianGrid, - Tooltip, - ResponsiveContainer, -} from "recharts"; +// Recharts is ~108 KiB Brotli — defer-import the scatter primitives so they +// only land in the bundle once a correlation card actually renders. Every +// ``/``/``/axis/grid/tooltip use +// site sits inside a `length >= 5` gate and is below the fold, so the first +// paint never waits on Recharts. v1.5 perf audit (docs/audit/v15-performance.md). +const ScatterChart = dynamic( + () => import("recharts").then((mod) => ({ default: mod.ScatterChart })), + { ssr: false }, +); +const Scatter = dynamic( + () => import("recharts").then((mod) => ({ default: mod.Scatter })), + { ssr: false }, +); +const XAxis = dynamic( + () => import("recharts").then((mod) => ({ default: mod.XAxis })), + { ssr: false }, +); +const YAxis = dynamic( + () => import("recharts").then((mod) => ({ default: mod.YAxis })), + { ssr: false }, +); +const CartesianGrid = dynamic( + () => import("recharts").then((mod) => ({ default: mod.CartesianGrid })), + { ssr: false }, +); +const Tooltip = dynamic( + () => import("recharts").then((mod) => ({ default: mod.Tooltip })), + { ssr: false }, +); +const ResponsiveContainer = dynamic( + () => + import("recharts").then((mod) => ({ default: mod.ResponsiveContainer })), + { ssr: false }, +); import { getBpTargets } from "@/lib/analytics/bp-targets"; import { buildTrafficRange,