mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 12:32:43 +00:00
perf(insights): defer below-fold mother-page blocks behind next/dynamic + bundle config hygiene
Lighthouse v1.4.33 flagged ~463 KiB of unused JS on first /insights
paint plus ~211 KiB of bundled JS as unminified. Two coordinated
moves close the gap without touching the Coach panel internals or
the dashboard tree.
next.config.ts
- `compiler.removeConsole` with `exclude: ["error", "warn"]` strips
the chart-wiring + Coach SSE breadcrumb literals from the prod
bundle. Error + warn paths still flow through to GlitchTip.
- `headers()` advertises `Permissions-Policy: unload=()` so Chromium
admits the page to the back/forward cache when navigating away. We
carry no `beforeunload` / `unload` listeners in-tree so the hint is
honest.
src/app/insights/page.tsx
- `<DailyBriefing>`, `<CorrelationRow>`, `<TrendsRow>` move behind
`next/dynamic({ ssr: false, loading: <Skeleton /> })`. All three
paint below the hero on every viewport and were eagerly imported
before. Each loader skeleton matches the dimensions of the resolved
block so layout doesn't shift.
- `<HeroStrip>` stays an eager import — the greeting + health-score
badge are the visible-first-paint surface.
This commit is contained in:
1 parent
432ebccdae
commit
d254e9cd98
2 files changed
+82
-3
No files matched your search
@@ -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",
|
||||
|
||||
@@ -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`. `<HeroStrip>` (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: () => (
|
||||
<div className="bg-card border-border h-48 animate-pulse rounded-xl border" />
|
||||
),
|
||||
},
|
||||
);
|
||||
const CorrelationRow = dynamic(
|
||||
() =>
|
||||
import("@/components/insights/correlation-row").then((mod) => ({
|
||||
default: mod.CorrelationRow,
|
||||
})),
|
||||
{
|
||||
ssr: false,
|
||||
loading: () => (
|
||||
<div className="bg-card border-border h-32 animate-pulse rounded-xl border" />
|
||||
),
|
||||
},
|
||||
);
|
||||
const TrendsRow = dynamic(
|
||||
() =>
|
||||
import("@/components/insights/trends-row").then((mod) => ({
|
||||
default: mod.TrendsRow,
|
||||
})),
|
||||
{
|
||||
ssr: false,
|
||||
loading: () => (
|
||||
<div className="bg-card border-border h-64 animate-pulse rounded-xl border" />
|
||||
),
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* v1.4.25 W4d — Insights mother page.
|
||||
*
|
||||
|
||||
Reference in new issue
Block a user