# Aether Svelte 5 App — Performance Review Date: 2026-01-26 This document collects findings from a quick code review of the Aether Svelte 5 app (focused on `src/routes/+layout.svelte`, `src/routes/+page.svelte`, and the events launcher layout). It lists concrete performance issues observed, their likely impact, remediation recommendations (quick wins first), code examples for safe refactors, measurement steps, and a prioritized action plan. **Scope** - Files inspected: `src/routes/+layout.svelte`, `src/routes/+page.svelte`, event launcher `+layout.svelte` and related store initialization logic. - Platform: SvelteKit with Vite (assumed from repo files). Builds use Tailwind CSS and manual global CSS. --- ## Summary of Key Findings - Large synchronous imports at module evaluation time (notably `highlight.js` and its stylesheet) increase initial bundle size and run before first paint. - Many components and icon imports are static at top-level even when rendered conditionally (`Analytics`, `MyClipboard`, `E_app_debug_menu`, `E_app_sys_menu`, multiple Lucide icons). This inflates the client bundle. - Significant initialization and cache logic runs synchronously during module evaluation: store writes, cache expiration checks, IndexedDB operations, localStorage population, calls to `invalidateAll()` and conditional `window.location.reload()` paths — these can block initial render and cause reload loops or extra round trips. - Dexie `liveQuery` usages in nested layouts are eager and may execute multiple DB queries on route entry, causing CPU and I/O work up-front. - Multiple synchronous CSS imports (global CSS + highlight CSS) can delay first paint and increase transfer size. - Many localStorage/sessionStorage writes and store updates are done immediately and synchronously; iterating many keys on startup can stall the main thread. - Analytics, WebSocket connections, and external network resource usage may be triggered too early. Impact: Slow Time to First Paint (TTFP), increased Time to Interactive (TTI), larger JS bundles, higher CPU main-thread blocking, worse Lighthouse/perceived performance. --- ## Concrete, Prioritized Recommendations Priority ordering: Quick wins (low code risk) → Short-term (moderate refactor) → Long-term (architectural). ### Quick Wins (apply first) - Lazy-load `highlight.js` and its CSS in `onMount` only when highlighting is needed. This removes a heavy library and stylesheet from the initial bundle. - Dynamically import non-critical components (`Analytics`, debug menus, clipboard UI, large icon modules) when they will actually render. - Move non-UI, non-blocking initialization to `onMount` and/or `requestIdleCallback` so SSR rendering and first paint aren't blocked by client-only logic. - Guard `invalidateAll()` / `window.location.reload()` so they run only after a user-visible prompt or on explicit action; avoid automatic reload on subtle mismatches. ### Short-Term Changes (next 1–2 days) - Defer expensive Dexie `liveQuery` and database queries until their containing route/section mounts, or add limits/pagination to reduce initial query size. - Batch localStorage writes (use a single write or `requestIdleCallback`/`setTimeout(…,0)`), or store a single object rather than many keys. - Replace heavy icon imports with an icon-on-demand strategy (SVG sprite, `unplugin-icons`, or inlining only icons used on first paint). - Lazy-load large CSS (e.g., highlight theme) with dynamic import or by adding/removing link elements. ### Long-Term / Architectural - Split routes to ensure route-level code-splitting is effective — verify that `+layout` doesn't import many route-specific modules. Keep `+layout.svelte` as thin as possible. - Use a bundle analyzer to identify largest modules and tune dependencies. - Serve production build with proper HTTP caching, compression (Brotli), and HTTP/2 or HTTP/3. - Consider server-side rendering vs. partial hydration boundaries (islands) for heavy interactive regions. --- ## Concrete Code Examples These are safe, copy-pasteable snippets for common quick fixes. 1) Lazy-load `highlight.js` in `src/routes/+layout.svelte` (move work to `onMount`): ```svelte ``` 2) Dynamic component import for `Analytics`: ```svelte {#if showAnalytics && Analytics} {/if} ``` 3) Batch localStorage writes using `requestIdleCallback` fallback: ```ts function batchSetLocalStorage(obj: Record) { const work = () => { for (const [k, v] of Object.entries(obj)) { try { localStorage.setItem(k, JSON.stringify(v)); } catch (e) {} } }; if ('requestIdleCallback' in window) { (window as any).requestIdleCallback(work); } else { setTimeout(work, 0); } } ``` 4) Avoid automatic reloads — example guard: ```ts // Only force reload after user consent or if critical mismatch if (flag_reload) { const reloadKey = 'aether_reload_shown_v' + ($ae_loc?.ver ?? 'unknown'); if (!localStorage.getItem(reloadKey)) { if (confirm('A new version is available. Reload now?')) { localStorage.setItem(reloadKey, '1'); location.reload(); } } } ``` --- ## Measurement & Tooling Run these locally to find concrete hotspots and bundle contributors. - Bundle analysis (Vite): ```bash # Build and open analyzer (if plugin configured) npm run build npx vite build --sourcemap # Use source-map-explorer or webpack-bundle-analyzer (adapt if using Rollup) npx source-map-explorer dist/assets/*.js ``` - Vite visualizer (install `rollup-plugin-visualizer` or `vite-plugin-visualizer`): ```bash # add plugin and run build with report # then open .html output from the plugin ``` - Lighthouse / DevTools: - Run Lighthouse (Performance, TTI, Largest Contentful Paint, Main thread) in Chrome. - In Performance panel record page load to find long main-thread tasks (JS parsing/execution) and identify blocking code paths. - Runtime marks: add `performance.mark('init-start')` / `performance.mark('init-end')` to measure areas (e.g., store population, Dexie queries) then `performance.measure()`. --- ## Prioritized Action Plan (Checklist) - [ ] Move `highlight.js` imports and theme stylesheet to `onMount` (quick win). - [ ] Convert `Analytics` and other developer tools to dynamic imports (quick win). - [ ] Move non-critical store population, IndexedDB maintenance, and cache-expiration logic to `onMount` or `requestIdleCallback`. - [ ] Add guard & confirmation around `invalidateAll()` and `location.reload()` to avoid reload loops. - [ ] Defer Dexie `liveQuery` initialization to the route or component that needs it (avoid running all live queries globally). - [ ] Batch localStorage writes and avoid iterating thousands of keys synchronously. - [ ] Run bundle analysis and produce a top-10 biggest modules list. - [ ] Replace heavy icon imports with icon-on-demand solution. - [ ] Re-run Lighthouse and validate improvements. --- ## Appendix: Quick Checklist for PRs - Ensure client-only code is inside `onMount` or guarded by `if (browser)`. - Where dynamic import used, use `` pattern. - Add performance marks around expensive blocks and log durations in dev mode. - Avoid unconditional network or socket connections in top-level layout; open them after user interaction or when component mounts. --- If you want, I can implement the first quick wins now: lazy-load `highlight.js` and convert `Analytics` import in `src/routes/+layout.svelte`, then run a local bundle analysis. Tell me which of the quick wins you'd like me to patch first and I'll start.