Skip to content

perf: lazy-load cache-manager to reduce initial bundle size#256

Merged
test3207 merged 2 commits intomainfrom
perf/lazy-load-cache-manager
Dec 21, 2025
Merged

perf: lazy-load cache-manager to reduce initial bundle size#256
test3207 merged 2 commits intomainfrom
perf/lazy-load-cache-manager

Conversation

@test3207
Copy link
Owner

Summary

Lazy-load the cache-manager module (which contains music-metadata ~100KB) to improve initial page load performance.

Changes

  • Dynamic import cache-manager in:
    • upload-form/index.tsx - only loads when user uploads files
    • library-service.ts - only loads when user deletes a library
  • Idle-time prefetch (prefetch.ts):
    • Uses requestIdleCallback to preload modules 3 seconds after app mount
    • Ensures modules are cached before user needs them
    • Non-blocking, doesn't affect FCP

Performance Results

Metric Before After Change
cache-manager chunk 105KB 1.3KB -99%
TBT 90ms 60ms -33%
CLS 0.114 0 Fixed
Lighthouse Score 79% 84% +5%

Note: FCP remains at 2.4s - the bottleneck is the main bundle size (383KB), not cache-manager.

Testing

  • Build succeeds
  • Lighthouse metrics improved
  • Upload functionality works (dynamic import tested)

Closes #220

- Dynamic import cache-manager in upload-form and library-service
- Add idle-time prefetch mechanism using requestIdleCallback
- Reduces cache-manager chunk from 105KB to 1.3KB in initial load
- music-metadata (~100KB) now loads only when needed

Improves TBT from 90ms to 60ms (-33%)
Fixes CLS from 0.114 to 0

Part of #220
Copilot AI review requested due to automatic review settings December 21, 2025 13:25
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements lazy-loading for the cache-manager module to reduce the initial bundle size by ~100KB and improve page load performance. The module is now dynamically imported only when needed (during file uploads or library deletion), with an idle-time prefetching strategy to ensure it's cached before users typically need it.

Key Changes:

  • Dynamic imports replace static imports for cache-manager in upload and library deletion flows
  • New prefetch.ts module uses requestIdleCallback to preload heavy modules during browser idle time
  • Prefetching is initialized in AuthProvider to warm the cache 3 seconds after app mount

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

File Description
frontend/src/lib/services/library-service.ts Replaces static import with lazy-loaded getCacheNameLazy function for cache operations during library deletion
frontend/src/lib/prefetch.ts New module implementing idle-time prefetching strategy using requestIdleCallback with setTimeout fallback
frontend/src/components/providers/auth-provider.tsx Initializes prefetch module on component mount to preload heavy modules after initial render
frontend/src/components/features/upload/upload-form/index.tsx Converts static import to dynamic import for caching functions, loading only when files are uploaded

Comment on lines +25 to +28
// Start idle-time prefetch of heavy modules after initial render
useEffect(() => {
startIdlePrefetch();
}, []);
Copy link

Copilot AI Dec 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The startIdlePrefetch function is called unconditionally in a useEffect with an empty dependency array, which means it will be called every time the AuthProvider mounts. However, the prefetchModule function already has a guard (prefetchedModules Set) that prevents duplicate prefetching. This guard won't work correctly if the module is ever unmounted and remounted because startIdlePrefetch() creates a new setTimeout each time. Consider either: 1) ensuring startIdlePrefetch itself checks if prefetching is already in progress/completed, or 2) documenting that the guard in prefetchModule is intentional and sufficient.

Copilot uses AI. Check for mistakes.
Comment on lines 16 to 22
let cachedGetCacheName: ((type: "audio" | "covers") => string) | null = null;
async function getCacheNameLazy(type: "audio" | "covers"): Promise<string> {
if (!cachedGetCacheName) {
const module = await import("@/lib/pwa/cache-manager");
cachedGetCacheName = module.getCacheName;
}
return cachedGetCacheName(type);
Copy link

Copilot AI Dec 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The getCacheNameLazy function caches the getCacheName function reference in a module-level variable. Since getCacheName is a simple function that just returns a constant (CACHE_NAME), this caching adds unnecessary complexity. The overhead saved is negligible compared to the dynamic import that's already happening. Consider simplifying by returning the getCacheName function call directly: return (await import("@/lib/pwa/cache-manager")).getCacheName(type); without the cachedGetCacheName variable.

Suggested change
let cachedGetCacheName: ((type: "audio" | "covers") => string) | null = null;
async function getCacheNameLazy(type: "audio" | "covers"): Promise<string> {
if (!cachedGetCacheName) {
const module = await import("@/lib/pwa/cache-manager");
cachedGetCacheName = module.getCacheName;
}
return cachedGetCacheName(type);
async function getCacheNameLazy(type: "audio" | "covers"): Promise<string> {
return (await import("@/lib/pwa/cache-manager")).getCacheName(type);

Copilot uses AI. Check for mistakes.
Comment on lines +30 to +31
if ("requestIdleCallback" in window) {
requestIdleCallback(callback, { timeout: 5000 });
Copy link

Copilot AI Dec 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TypeScript does not have built-in type definitions for requestIdleCallback. This code will cause a TypeScript compilation error. You need to add type declarations for requestIdleCallback to the project's type definitions file (e.g., vite-env.d.ts) to properly type this API.

Copilot uses AI. Check for mistakes.
@github-actions
Copy link
Contributor

Coverage after merging perf/lazy-load-cache-manager into main will be

78.72%

Coverage Report
FileStmtsBranchesFuncsLinesUncovered Lines
100%100%100%100%
src/lib
   events.ts100%100%100%100%
   logger-client.ts82.61%60%100%100%15, 21, 27, 8
src/lib/audio
   media-session.ts74.84%90.91%71.43%65.12%105–106, 124, 135–137, 144–146, 148, 155–157, 159, 167–169, 171, 178–180, 182, 198, 212, 226, 237, 245–246, 259, 44–45, 76, 86–87, 93
   queue.ts86.15%82.54%92%86.92%210–211, 223, 225, 225, 225–226, 228, 249–252, 303, 313, 313, 313–314, 316, 37–38, 54–55, 72, 76–77
src/lib/offline-proxy/routes
   libraries.ts42.57%24.32%66.67%51.72%122–123, 208, 248, 261–263, 266, 268, 268, 268–269, 278, 278, 278, 278, 278–279, 288–289, 291, 291, 291–292, 296, 299–300, 303, 305, 305, 305, 307, 311, 311–312, 312, 316, 320, 323, 326, 326, 326–327, 338, 341, 341, 341, 341, 341–342, 344, 349, 353, 356, 358, 361, 361–362, 362–363, 363–364, 364–365, 365–366, 366, 366, 366–367, 367–368, 368, 370, 370, 370, 370, 373, 373, 385–386, 389, 394, 394, 394–396, 396, 402, 412–413, 419–422, 422, 422, 424, 426, 426, 426, 426, 426–427, 437, 440, 442, 447, 61, 90, 90–91
   player.ts47.26%35%57.14%55.70%101, 137, 149–150, 153, 158, 160, 164, 166, 166, 166–168, 170, 170, 170–171, 179, 179–180, 180, 194, 199–200, 206–207, 207, 207–208, 216, 216–217, 217, 230, 235, 258, 279–282, 293, 305–308, 310, 312, 312, 312–313, 313, 313–314, 314, 314–315, 315, 315, 319, 321, 326, 56–58, 58, 58, 58, 58–59, 63–64, 71, 73, 93
   playlists.ts86.27%92.31%91.67%84.35%100, 119, 136, 153, 173, 187, 193–194, 213, 226, 232, 57, 63–68, 70, 87
   songs.ts89.66%75%100%94.87%102, 104, 111, 113, 139–140
src/lib/offline-proxy/utils
   auth.ts93.33%100%100%89.47%70–71
   index.ts100%100%100%100%
   sorting.ts100%100%100%100%
src/lib/storage
   storage-constants.ts100%100%100%100%
src/lib/utils
   audio-filter.ts100%100%100%100%
   defaults.ts100%100%100%100%
   format-duration.ts100%100%100%100%
   format.ts100%100%100%100%
   hash.ts88.24%100%100%85.19%64–65, 87–88
   uuid.ts90%87.50%83.33%93.75%31–32
src/locales
   i18n.ts95.45%87.50%100%97.37%23, 40–41

@test3207 test3207 merged commit 2a0fff0 into main Dec 21, 2025
4 checks passed
@test3207 test3207 deleted the perf/lazy-load-cache-manager branch December 22, 2025 09:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Improve FCP from 3.3s to < 1.8s

2 participants