mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 04:50:59 +00:00
perf(charts): one shared async boundary for the whole recharts surface
Every next/dynamic chart import used to point at its own module, so Turbopack emitted a separate chunk group per boundary — the production build carried EIGHT ~312 KB copies of the recharts module graph, and /insights (the worst-LCP route) downloaded two of them (~92 KB gz of pure duplication, one fully unused at load). All chart loaders now resolve the single chart-runtime barrel (src/components/charts/chart-runtime.ts), which owns the only path to recharts: one shared chunk group, recharts exists exactly once in the build (verified: 8 -> 1 recharts-fingerprinted chunks), cached across routes. Charts stay recharts and stay pixel-identical; every call site keeps its own matching skeleton and ssr:false flag. Riders of the same rule: - the mood-trio barrel (mood-charts.ts, the v1.16.7 precedent this generalises) folds into the runtime and is removed - the vitals sparkline body extracts to delta-sparkline.tsx and loads through the runtime; the tile keeps the fixed 40 px band, so the async gap is an identically-sized empty box - /cycle's BBT chart (the last static recharts import on a live route) defers through the runtime with a card-matching skeleton H3
This commit is contained in:
1 parent
788aac5837
commit
e260b2d5c4
20 files changed
+165
-74
No files matched your search
@@ -30,7 +30,7 @@ import { apiGet } from "@/lib/api/api-fetch";
|
||||
*/
|
||||
const MoodChart = dynamic(
|
||||
() =>
|
||||
import("@/components/charts/mood-chart").then((mod) => ({
|
||||
import("@/components/charts/chart-runtime").then((mod) => ({
|
||||
default: mod.MoodChart,
|
||||
})),
|
||||
{ ssr: false, loading: () => <ChartSkeleton /> },
|
||||
|
||||
+2
-2
@@ -81,7 +81,7 @@ const BATCH_COVERAGE_DAYS = 31;
|
||||
// to ONE error card instead of bubbling to the route-level `error.tsx`.
|
||||
const MoodChartLazy = dynamic(
|
||||
() =>
|
||||
importWithRetry(() => import("@/components/charts/mood-chart")).then(
|
||||
importWithRetry(() => import("@/components/charts/chart-runtime")).then(
|
||||
(mod) => ({ default: mod.MoodChart }),
|
||||
),
|
||||
{ ssr: false, loading: () => <ChartSkeleton /> },
|
||||
@@ -94,7 +94,7 @@ const MoodChart = (props: React.ComponentProps<typeof MoodChartLazy>) => (
|
||||
const MedicationComplianceChartLazy = dynamic(
|
||||
() =>
|
||||
importWithRetry(
|
||||
() => import("@/components/charts/medication-compliance-chart"),
|
||||
() => import("@/components/charts/chart-runtime"),
|
||||
).then((mod) => ({ default: mod.MedicationComplianceChart })),
|
||||
{ ssr: false, loading: () => <ChartSkeleton /> },
|
||||
);
|
||||
|
||||
@@ -32,7 +32,7 @@ import { StatusItem, usePublicVersion, useSystemStatus } from "./_shared";
|
||||
// working — same pattern the insights page uses for the scatter chart.
|
||||
const HostMetricsChart = dynamic(
|
||||
() =>
|
||||
import("./host-metrics-chart").then((mod) => ({
|
||||
import("@/components/charts/chart-runtime").then((mod) => ({
|
||||
default: mod.HostMetricsChart,
|
||||
})),
|
||||
{
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* The ONE async boundary for every recharts-rendering component.
|
||||
*
|
||||
* Every `next/dynamic` chart import in the tree points at THIS module
|
||||
* (`import("@/components/charts/chart-runtime")`), never at the individual
|
||||
* chart files. Pointing N dynamic boundaries at N different modules made
|
||||
* Turbopack emit a separate chunk group per boundary, each carrying its own
|
||||
* full copy of the recharts module graph — the production build shipped
|
||||
* EIGHT ~312 KB copies of the library, and /insights alone downloaded two
|
||||
* (~92 KB gz of pure duplication on the worst-LCP route). One shared import
|
||||
* target ⇒ one shared chunk group ⇒ recharts exists exactly once, cached
|
||||
* across every route.
|
||||
*
|
||||
* Extends the v1.16.7 `mood-charts.ts` precedent (which did this for the
|
||||
* mood trio) to the whole chart surface; that scoped barrel is folded in
|
||||
* here.
|
||||
*
|
||||
* Rules:
|
||||
* - A new recharts consumer is added HERE and loaded through
|
||||
* `dynamic(() => import("@/components/charts/chart-runtime").then(...))`
|
||||
* at its call site — with the call site's own matching skeleton.
|
||||
* - No component outside this module's static import graph may import
|
||||
* "recharts" directly.
|
||||
* - Types stay importable from the component files directly (type-only
|
||||
* imports are value-free and don't drag the chunk in).
|
||||
*/
|
||||
|
||||
export { HealthChart } from "./health-chart";
|
||||
export { MoodChart } from "./mood-chart";
|
||||
export { MedicationComplianceChart } from "./medication-compliance-chart";
|
||||
export { ScatterCorrelationChart } from "./scatter-correlation-chart";
|
||||
export { HostMetricsChart } from "@/components/admin/host-metrics-chart";
|
||||
export { CustomMetricChart } from "@/components/custom-metrics/custom-metric-chart";
|
||||
export { BbtChart } from "@/components/cycle/bbt-chart";
|
||||
export { DeltaSparkline } from "@/components/insights/derived/delta-sparkline";
|
||||
export { MoodDistributionChart } from "@/components/insights/mood/mood-distribution-chart";
|
||||
export { MoodTimeOfDayChart } from "@/components/insights/mood/mood-time-of-day-chart";
|
||||
export { MoodWeekdayChart } from "@/components/insights/mood/mood-weekday-chart";
|
||||
export { SleepStageStackedBar } from "@/components/insights/sleep-stage-stacked-bar";
|
||||
export { LabBiomarkerChart } from "@/components/labs/lab-biomarker-chart";
|
||||
export { DoseStrengthCurve } from "@/components/medications/dose-strength-curve";
|
||||
export { DrugLevelChart } from "@/components/medications/drug-level-chart";
|
||||
export { AssessmentHistoryChart } from "@/components/mental-health/assessment-history-chart";
|
||||
@@ -14,7 +14,7 @@ import { importWithRetry } from "@/lib/retry-import";
|
||||
* Pre-fix, eight call sites (`src/app/page.tsx`, five insights
|
||||
* sub-pages, `sleep-duration-chart.tsx`, `trends-row.tsx`, and
|
||||
* `vo2-max-chart-row.tsx`) duplicated the same
|
||||
* `dynamic(() => import("@/components/charts/health-chart"), { ... })`
|
||||
* `dynamic(() => import("@/components/charts/chart-runtime"), { ... })`
|
||||
* incantation. The duplication meant the loading skeleton + `ssr: false`
|
||||
* flag had to be re-wired on every caller.
|
||||
*
|
||||
@@ -31,7 +31,7 @@ import { importWithRetry } from "@/lib/retry-import";
|
||||
*/
|
||||
const HealthChartLazy = dynamic(
|
||||
() =>
|
||||
importWithRetry(() => import("@/components/charts/health-chart")).then(
|
||||
importWithRetry(() => import("@/components/charts/chart-runtime")).then(
|
||||
(mod) => ({ default: mod.HealthChart }),
|
||||
),
|
||||
{ ssr: false, loading: () => <ChartSkeleton /> },
|
||||
@@ -59,7 +59,7 @@ export function HealthChartDynamic(
|
||||
*/
|
||||
const HealthChartLazyMini = dynamic(
|
||||
() =>
|
||||
importWithRetry(() => import("@/components/charts/health-chart")).then(
|
||||
importWithRetry(() => import("@/components/charts/chart-runtime")).then(
|
||||
(mod) => ({ default: mod.HealthChart }),
|
||||
),
|
||||
{ ssr: false, loading: () => <ChartSkeleton mini /> },
|
||||
|
||||
@@ -42,7 +42,7 @@ import type {
|
||||
// off the detail page's first-load JS; the skeleton shell holds the layout.
|
||||
const CustomMetricChartLazy = dynamic(
|
||||
() =>
|
||||
importWithRetry(() => import("./custom-metric-chart")).then((mod) => ({
|
||||
importWithRetry(() => import("@/components/charts/chart-runtime")).then((mod) => ({
|
||||
default: mod.CustomMetricChart,
|
||||
})),
|
||||
{ ssr: false, loading: () => <ChartSkeleton /> },
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
"use client";
|
||||
|
||||
import { useMemo, useState } from "react";
|
||||
import dynamic from "next/dynamic";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import { Loader2, Plus, RefreshCw, Sparkles } from "lucide-react";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { PageHeader } from "@/components/ui/page-header";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { CycleRing } from "./cycle-ring";
|
||||
import { BbtChart } from "./bbt-chart";
|
||||
import { CycleCalendar } from "./cycle-calendar";
|
||||
import { CycleHistoryChart } from "./cycle-history-chart";
|
||||
import { LogDaySheet } from "./log-day-sheet";
|
||||
@@ -54,6 +55,33 @@ function shiftToday(days: number): string {
|
||||
|
||||
/** The four cycle tabs, in render order. The deep-link value is validated
|
||||
* against this set so a junk `?tab=` falls back to the calendar tab. */
|
||||
// The BBT chart is the only recharts consumer on /cycle — load it through
|
||||
// the shared chart-runtime boundary so the route stops carrying its own
|
||||
// copy of the recharts module graph. The loading shell mirrors the card
|
||||
// the chart paints (header row + caption + 210 px chart band inside the
|
||||
// standard card chrome) so the tab layout holds still while the shared
|
||||
// chunk streams.
|
||||
const BbtChart = dynamic(
|
||||
() =>
|
||||
import("@/components/charts/chart-runtime").then((mod) => ({
|
||||
default: mod.BbtChart,
|
||||
})),
|
||||
{
|
||||
ssr: false,
|
||||
loading: () => (
|
||||
<div className="bg-card border-border rounded-xl border py-6">
|
||||
<div className="px-6">
|
||||
<Skeleton className="h-5 w-44" />
|
||||
</div>
|
||||
<div className="mt-6 px-6">
|
||||
<Skeleton className="mb-2 h-4 w-64" />
|
||||
<Skeleton className="h-[210px] w-full rounded-md" />
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
);
|
||||
|
||||
const CYCLE_TABS = ["calendar", "predictions", "insights", "settings"] as const;
|
||||
type CycleTab = (typeof CYCLE_TABS)[number];
|
||||
|
||||
|
||||
@@ -47,10 +47,12 @@ vi.mock("next/dynamic", async () => {
|
||||
const mood = await import("@/components/charts/mood-chart");
|
||||
return {
|
||||
default: (loader: unknown) => {
|
||||
// Identify which chart the loader targets by its source text.
|
||||
// Identify which chart the loader targets by its source text. Every
|
||||
// loader now resolves the shared chart-runtime boundary and picks a
|
||||
// named export off it, so the export name is the discriminator.
|
||||
const src = String(loader);
|
||||
const Comp = (
|
||||
src.includes("mood-chart") ? mood.MoodChart : health.HealthChart
|
||||
src.includes("MoodChart") ? mood.MoodChart : health.HealthChart
|
||||
) as React.ComponentType<Record<string, unknown>>;
|
||||
return (props: Record<string, unknown>) => <Comp {...props} />;
|
||||
},
|
||||
|
||||
@@ -29,7 +29,7 @@ import { AskCoachAction } from "./ask-coach-action";
|
||||
|
||||
const ScatterCorrelationChart = dynamic(
|
||||
() =>
|
||||
import("@/components/charts/scatter-correlation-chart").then((mod) => ({
|
||||
import("@/components/charts/chart-runtime").then((mod) => ({
|
||||
default: mod.ScatterCorrelationChart,
|
||||
})),
|
||||
{
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
"use client";
|
||||
|
||||
import { useId } from "react";
|
||||
import { Area, AreaChart, ResponsiveContainer, YAxis } from "recharts";
|
||||
|
||||
/**
|
||||
* The recharts body of the `<SparklineDeltaTile>` inline sparkline,
|
||||
* extracted so the tile itself carries no static recharts import. The tile
|
||||
* loads this through the shared chart-runtime boundary
|
||||
* (`@/components/charts/chart-runtime`); its fixed 40 px container is owned
|
||||
* by the tile, so the async gap paints an empty band of identical size —
|
||||
* zero layout shift, pixel-identical once mounted.
|
||||
*/
|
||||
export function DeltaSparkline({
|
||||
data,
|
||||
strokeVar,
|
||||
}: {
|
||||
/** `{ i, v }` points, already length-guarded (≥2) by the tile. */
|
||||
data: Array<{ i: number; v: number }>;
|
||||
/** CSS var expression for the sentiment stroke, e.g. `var(--success)`. */
|
||||
strokeVar: string;
|
||||
}) {
|
||||
// A stable per-instance id for the gradient <defs>. Deriving it from a
|
||||
// label slug collides when two tiles share a localized label (the gradient
|
||||
// fill on the second tile would not resolve); useId() is collision-free.
|
||||
const fillId = useId();
|
||||
return (
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<AreaChart data={data} margin={{ top: 2, right: 0, bottom: 2, left: 0 }}>
|
||||
<defs>
|
||||
<linearGradient id={fillId} x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stopColor={strokeVar} stopOpacity={0.28} />
|
||||
<stop offset="100%" stopColor={strokeVar} stopOpacity={0} />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<YAxis hide domain={["dataMin", "dataMax"]} />
|
||||
<Area
|
||||
type="monotone"
|
||||
dataKey="v"
|
||||
stroke={strokeVar}
|
||||
strokeWidth={1.5}
|
||||
fill={`url(#${fillId})`}
|
||||
isAnimationActive={false}
|
||||
dot={false}
|
||||
/>
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
);
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useId } from "react";
|
||||
import dynamic from "next/dynamic";
|
||||
import { ArrowDown, ArrowRight, ArrowUp, Minus } from "lucide-react";
|
||||
import { Area, AreaChart, ResponsiveContainer, YAxis } from "recharts";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useTranslations, useFormatters } from "@/lib/i18n/context";
|
||||
import {
|
||||
@@ -28,6 +27,19 @@ import {
|
||||
* Fixed footprint so a tile never reflows its neighbours.
|
||||
*/
|
||||
|
||||
// The recharts sparkline body loads through the shared chart-runtime
|
||||
// boundary so this tile (statically imported by the vitals dashboard)
|
||||
// carries no recharts in its own chunk group. The tile owns the fixed
|
||||
// 40 px container, so the async gap is an identically-sized empty band —
|
||||
// no layout shift, no skeleton needed.
|
||||
const DeltaSparkline = dynamic(
|
||||
() =>
|
||||
import("@/components/charts/chart-runtime").then((mod) => ({
|
||||
default: mod.DeltaSparkline,
|
||||
})),
|
||||
{ ssr: false, loading: () => null },
|
||||
);
|
||||
|
||||
export interface SparklineDeltaTileProps {
|
||||
label: string;
|
||||
/** Latest reading; `null` renders the empty state. */
|
||||
@@ -88,10 +100,6 @@ export function SparklineDeltaTile({
|
||||
}: SparklineDeltaTileProps) {
|
||||
const { t } = useTranslations();
|
||||
const fmt = useFormatters();
|
||||
// A stable per-instance id for the gradient <defs>. Deriving it from the
|
||||
// label slug collides when two tiles share a localized label (the gradient
|
||||
// fill on the second tile would not resolve); useId() is collision-free.
|
||||
const fillId = useId();
|
||||
|
||||
const arrowSentiment = getTrendSentiment(delta ?? null, directionSentiment);
|
||||
const trendColor = sentimentColorClass(arrowSentiment);
|
||||
@@ -190,29 +198,7 @@ export function SparklineDeltaTile({
|
||||
className="mt-3 h-10 w-full"
|
||||
data-slot="sparkline-delta-tile-spark"
|
||||
>
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<AreaChart
|
||||
data={sparkData}
|
||||
margin={{ top: 2, right: 0, bottom: 2, left: 0 }}
|
||||
>
|
||||
<defs>
|
||||
<linearGradient id={fillId} x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stopColor={strokeVar} stopOpacity={0.28} />
|
||||
<stop offset="100%" stopColor={strokeVar} stopOpacity={0} />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<YAxis hide domain={["dataMin", "dataMax"]} />
|
||||
<Area
|
||||
type="monotone"
|
||||
dataKey="v"
|
||||
stroke={strokeVar}
|
||||
strokeWidth={1.5}
|
||||
fill={`url(#${fillId})`}
|
||||
isAnimationActive={false}
|
||||
dot={false}
|
||||
/>
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
<DeltaSparkline data={sparkData} strokeVar={strokeVar} />
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
/**
|
||||
* v1.16.7 — single async boundary for the three mood mini-charts.
|
||||
*
|
||||
* The distribution / weekday / time-of-day charts were each deferred
|
||||
* through their own `next/dynamic` import, which produced three separate
|
||||
* chunk groups: the cards popped in one after another as each chunk
|
||||
* landed (incoherent reveal on the mood insights page), and each group
|
||||
* carried its own copy of the Recharts module graph. Funnelling all
|
||||
* three through this one barrel gives them a single shared chunk — they
|
||||
* arrive (and reveal) together, and Recharts is bundled once for the
|
||||
* trio.
|
||||
*
|
||||
* Consumers keep importing the chart types directly from the component
|
||||
* files (type-only imports are value-free and don't drag the chunk in).
|
||||
*/
|
||||
export { MoodDistributionChart } from "./mood-distribution-chart";
|
||||
export { MoodWeekdayChart } from "./mood-weekday-chart";
|
||||
export { MoodTimeOfDayChart } from "./mood-time-of-day-chart";
|
||||
@@ -25,7 +25,7 @@ import { MoodExplainerIcon } from "./mood-explainer-icon";
|
||||
|
||||
const ScatterCorrelationChart = dynamic(
|
||||
() =>
|
||||
import("@/components/charts/scatter-correlation-chart").then((mod) => ({
|
||||
import("@/components/charts/chart-runtime").then((mod) => ({
|
||||
default: mod.ScatterCorrelationChart,
|
||||
})),
|
||||
{
|
||||
|
||||
@@ -36,12 +36,13 @@ import { MoodHeatmap } from "@/components/charts/mood-heatmap";
|
||||
import type { MoodDistributionRow } from "./mood-distribution-chart";
|
||||
import type { MoodWeekdayRow } from "./mood-weekday-chart";
|
||||
import type { MoodTimeOfDayPattern } from "./mood-time-of-day-chart";
|
||||
// v1.16.7 — all three loaders resolve the ONE `./mood-charts` barrel so
|
||||
// they share a single chunk: the cards reveal together instead of
|
||||
// popping in chunk-by-chunk, and Recharts ships once for the trio.
|
||||
// v1.16.7 established the shared-barrel pattern for the trio; all three
|
||||
// loaders now resolve the app-wide chart-runtime boundary, so they share
|
||||
// the ONE recharts chunk with every other chart surface: the cards reveal
|
||||
// together, and recharts ships exactly once for the whole app.
|
||||
const MoodDistributionChart = dynamic(
|
||||
() =>
|
||||
import("./mood-charts").then((mod) => ({
|
||||
import("@/components/charts/chart-runtime").then((mod) => ({
|
||||
default: mod.MoodDistributionChart,
|
||||
})),
|
||||
{
|
||||
@@ -53,7 +54,7 @@ const MoodDistributionChart = dynamic(
|
||||
);
|
||||
const MoodWeekdayChart = dynamic(
|
||||
() =>
|
||||
import("./mood-charts").then((mod) => ({
|
||||
import("@/components/charts/chart-runtime").then((mod) => ({
|
||||
default: mod.MoodWeekdayChart,
|
||||
})),
|
||||
{
|
||||
@@ -65,7 +66,7 @@ const MoodWeekdayChart = dynamic(
|
||||
);
|
||||
const MoodTimeOfDayChart = dynamic(
|
||||
() =>
|
||||
import("./mood-charts").then((mod) => ({
|
||||
import("@/components/charts/chart-runtime").then((mod) => ({
|
||||
default: mod.MoodTimeOfDayChart,
|
||||
})),
|
||||
{
|
||||
|
||||
@@ -35,7 +35,7 @@ import { ConfidenceMeter } from "./confidence-meter";
|
||||
*/
|
||||
const HealthChartLazy = dynamic(
|
||||
() =>
|
||||
importWithRetry(() => import("@/components/charts/health-chart")).then(
|
||||
importWithRetry(() => import("@/components/charts/chart-runtime")).then(
|
||||
(mod) => ({ default: mod.HealthChart }),
|
||||
),
|
||||
{ ssr: false, loading: () => <ChartSkeleton mini /> },
|
||||
@@ -48,7 +48,7 @@ const HealthChart = (props: ComponentProps<typeof HealthChartLazy>) => (
|
||||
|
||||
const MoodChartLazy = dynamic(
|
||||
() =>
|
||||
importWithRetry(() => import("@/components/charts/mood-chart")).then(
|
||||
importWithRetry(() => import("@/components/charts/chart-runtime")).then(
|
||||
(mod) => ({ default: mod.MoodChart }),
|
||||
),
|
||||
{ ssr: false, loading: () => <ChartSkeleton mini /> },
|
||||
|
||||
@@ -25,7 +25,7 @@ import { SleepDurationChart } from "./sleep-duration-chart";
|
||||
// matches the card the bar paints so the layout stays stable.
|
||||
const SleepStageStackedBarLazy = dynamic(
|
||||
() =>
|
||||
importWithRetry(() => import("./sleep-stage-stacked-bar")).then((mod) => ({
|
||||
importWithRetry(() => import("@/components/charts/chart-runtime")).then((mod) => ({
|
||||
default: mod.SleepStageStackedBar,
|
||||
})),
|
||||
{ ssr: false, loading: () => <ChartSkeleton /> },
|
||||
|
||||
@@ -58,7 +58,7 @@ import {
|
||||
|
||||
const MoodChart = dynamic(
|
||||
() =>
|
||||
import("@/components/charts/mood-chart").then((mod) => ({
|
||||
import("@/components/charts/chart-runtime").then((mod) => ({
|
||||
default: mod.MoodChart,
|
||||
})),
|
||||
{ ssr: false, loading: () => <ChartSkeleton mini /> },
|
||||
|
||||
@@ -51,7 +51,7 @@ import type {
|
||||
// loading shell matches the in-card chart footprint so the layout is stable.
|
||||
const LabBiomarkerChartLazy = dynamic(
|
||||
() =>
|
||||
importWithRetry(() => import("./lab-biomarker-chart")).then((mod) => ({
|
||||
importWithRetry(() => import("@/components/charts/chart-runtime")).then((mod) => ({
|
||||
default: mod.LabBiomarkerChart,
|
||||
})),
|
||||
{ ssr: false, loading: () => <ChartSkeleton /> },
|
||||
|
||||
@@ -89,14 +89,14 @@ import type { ComplianceDisplay } from "@/lib/analytics/compliance";
|
||||
// shared chart skeleton standing in while the chunk streams.
|
||||
const DrugLevelChart = dynamic(
|
||||
() =>
|
||||
import("@/components/medications/drug-level-chart").then((mod) => ({
|
||||
import("@/components/charts/chart-runtime").then((mod) => ({
|
||||
default: mod.DrugLevelChart,
|
||||
})),
|
||||
{ ssr: false, loading: () => <ChartSkeleton /> },
|
||||
);
|
||||
const DoseStrengthCurve = dynamic(
|
||||
() =>
|
||||
import("@/components/medications/dose-strength-curve").then((mod) => ({
|
||||
import("@/components/charts/chart-runtime").then((mod) => ({
|
||||
default: mod.DoseStrengthCurve,
|
||||
})),
|
||||
{ ssr: false, loading: () => <ChartSkeleton /> },
|
||||
|
||||
@@ -38,7 +38,7 @@ import type { AssessmentRow, CrisisSet, InstrumentId } from "./types";
|
||||
// holds the layout.
|
||||
const AssessmentHistoryChartLazy = dynamic(
|
||||
() =>
|
||||
importWithRetry(() => import("./assessment-history-chart")).then((mod) => ({
|
||||
importWithRetry(() => import("@/components/charts/chart-runtime")).then((mod) => ({
|
||||
default: mod.AssessmentHistoryChart,
|
||||
})),
|
||||
{ ssr: false, loading: () => <ChartSkeleton /> },
|
||||
|
||||
Reference in new issue
Block a user