From 8658ec05c30a27daf2eb723fffe0152364b3b87a Mon Sep 17 00:00:00 2001 From: Jared Vititoe Date: Sat, 19 Sep 2026 12:59:28 -0400 Subject: [PATCH] perf: lazy-load seasonal overlays as their own chunks (#167) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All 11 seasonal overlays (particles + vanilla-extract keyframes) were in the main JS and CSS for everyone year-round. Each is now a React.lazy chunk fetched only when that season is active (auto date or override) or previewed in Settings → Appearance; the schedule/override decision stays in the main bundle. check-bundle-size: largest chunk 332.4 kB → 320.1 kB gzip; main CSS 68.4 kB → 56.5 kB; total gzip 1551.6 kB → 1561.3 kB (per-chunk overhead, only paid when a season is on). Verified headless: no theme chunk requested at startup with 'auto' out of season; the Settings grid fetches the 11 previews; picking Halloween renders the full-screen overlay (20 particle nodes). Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA --- .../components/seasonal/SeasonalEffect.tsx | 56 ++++++++++++++----- 1 file changed, 42 insertions(+), 14 deletions(-) diff --git a/src/app/components/seasonal/SeasonalEffect.tsx b/src/app/components/seasonal/SeasonalEffect.tsx index caf40d9ff..1d570fe66 100644 --- a/src/app/components/seasonal/SeasonalEffect.tsx +++ b/src/app/components/seasonal/SeasonalEffect.tsx @@ -1,21 +1,49 @@ -import React, { useEffect, useMemo, useState } from 'react'; +import React, { Suspense, useEffect, useMemo, useState } from 'react'; import { useAtomValue } from 'jotai'; import { settingsAtom } from '../../state/settings'; import { useReducedMotion } from '../../hooks/useReducedMotion'; import { zIndices } from '../../styles/zIndex'; import { SeasonTheme } from './types'; import { resolveSeasonTheme } from './seasonSchedule'; -import { HalloweenOverlay } from './themes/Halloween'; -import { ChristmasOverlay } from './themes/Christmas'; -import { NewYearOverlay } from './themes/NewYear'; -import { AutumnOverlay } from './themes/Autumn'; -import { AprilFoolsOverlay } from './themes/AprilFools'; -import { LunarNewYearOverlay } from './themes/LunarNewYear'; -import { ValentinesOverlay } from './themes/Valentines'; -import { StPatricksOverlay } from './themes/StPatricks'; -import { EarthDayOverlay } from './themes/EarthDay'; -import { DeepSpaceOverlay } from './themes/DeepSpace'; -import { ArcadeOverlay } from './themes/Arcade'; + +// [Gitea #167] Each overlay (its particles + keyframes) is its own chunk, +// fetched only when that season is active or previewed in Settings — the +// date/override decision stays in the main bundle. +const lazyOverlay = (load: () => Promise<{ default: React.ComponentType<{ reduced: boolean }> }>) => + React.lazy(load); +const HalloweenOverlay = lazyOverlay(() => + import('./themes/Halloween').then((m) => ({ default: m.HalloweenOverlay })), +); +const ChristmasOverlay = lazyOverlay(() => + import('./themes/Christmas').then((m) => ({ default: m.ChristmasOverlay })), +); +const NewYearOverlay = lazyOverlay(() => + import('./themes/NewYear').then((m) => ({ default: m.NewYearOverlay })), +); +const AutumnOverlay = lazyOverlay(() => + import('./themes/Autumn').then((m) => ({ default: m.AutumnOverlay })), +); +const AprilFoolsOverlay = lazyOverlay(() => + import('./themes/AprilFools').then((m) => ({ default: m.AprilFoolsOverlay })), +); +const LunarNewYearOverlay = lazyOverlay(() => + import('./themes/LunarNewYear').then((m) => ({ default: m.LunarNewYearOverlay })), +); +const ValentinesOverlay = lazyOverlay(() => + import('./themes/Valentines').then((m) => ({ default: m.ValentinesOverlay })), +); +const StPatricksOverlay = lazyOverlay(() => + import('./themes/StPatricks').then((m) => ({ default: m.StPatricksOverlay })), +); +const EarthDayOverlay = lazyOverlay(() => + import('./themes/EarthDay').then((m) => ({ default: m.EarthDayOverlay })), +); +const DeepSpaceOverlay = lazyOverlay(() => + import('./themes/DeepSpace').then((m) => ({ default: m.DeepSpaceOverlay })), +); +const ArcadeOverlay = lazyOverlay(() => + import('./themes/Arcade').then((m) => ({ default: m.ArcadeOverlay })), +); // SeasonTheme + the date-window logic now live in leaf modules (single source // of truth, shared with the settings UI). Re-exported here for existing @@ -69,7 +97,7 @@ function SeasonalOverlay({ theme, reduced }: { theme: SeasonTheme; reduced: bool overflow: 'hidden', }} > - {buildOverlayContent(theme, reduced)} + {buildOverlayContent(theme, reduced)} ); } @@ -94,7 +122,7 @@ export function SeasonalPreview({ theme }: { theme: SeasonTheme }) { containerType: 'inline-size', }} > - {buildOverlayContent(theme, true)} + {buildOverlayContent(theme, true)} ); }