mirror of
https://github.com/koala73/worldmonitor.git
synced 2026-04-26 01:24:59 +02:00
Replace "WorldMonitor" with "World Monitor" in all user-facing display text across blog posts, docs, layouts, structured data, footer, offline page, and X-Title headers. Technical identifiers (User-Agent strings, X-WorldMonitor-Key headers, @WorldMonitorApp handle, function names) are preserved unchanged. Also adds anchors color to Mintlify docs config to fix blue link color in dark mode.
29 KiB
29 KiB
World Monitor — Performance Optimization Roadmap
All items below target end-user perceived speed: faster initial load, smoother panel rendering, lower memory footprint, and snappier map interactions. Items are ordered roughly by expected impact.
Priority: 🔴 High impact · 🟡 Medium impact · 🟢 Low impact (polish).
Status: · 🔄 Partial · ❌ Not started
🔴 Critical Path — First Load & Time to Interactive
PERF-001 — Code-Split Panels into Lazy-Loaded Chunks
- Impact: 🔴 High | Effort: ~2 days
- Status: —
vite.config.tsmanualChunkssplits panel components into a dedicatedpanelschunk, loaded in parallel with the main bundle for better caching and reduced initial parse time. App.tsstatically imports all 35+ panel components, bloating the main bundle to ~1.5 MB.- Split each panel into a dynamic
import()and only load when the user enables that panel. - Implementation: Wrap each panel constructor in
App.tswithawait import('@/components/FooPanel'). Use Vite's built-in chunk splitting. - Expected gain: Reduce initial JS payload by 40–60%.
PERF-002 — Tree-Shake Unused Locale Files
- Impact: 🔴 High | Effort: ~4 hours
- Status: —
src/services/i18n.tsuses per-language dynamicimport()viaLOCALE_LOADERS. Onlyen.jsonis bundled eagerly; all other locales are lazy-loaded on demand. - All 13 locale JSON files are bundled, but the user only needs 1 at a time.
- Dynamically
import(@/locales/${lang}.json)only the active language. Pre-load the fallback (en.json) and lazy-load the rest. - Expected gain: Save ~500 KB from initial bundle.
PERF-003 — Defer Non-Critical API Calls
- Impact: 🔴 High | Effort: ~1 day
- Status: —
src/utils/index.tsprovidesdeferToIdle()usingrequestIdleCallbackwithsetTimeoutfallback.App.loadAllData()defers non-critical fetches (UCDP, displacement, climate, fires, stablecoins, cable activity) by 5 seconds, keeping news/markets/conflicts/CII as priority. App.init()fires ~30 fetch calls simultaneously on startup. Most are background data (UCDP, displacement, climate, fires, stablecoins).- Prioritize: map tiles + conflicts + news + CII. Defer everything else by 5–10 seconds using
requestIdleCallback. - Expected gain: Reduce Time to Interactive by 2–3 seconds on slow connections.
PERF-004 — Pre-Render Critical CSS / Above-the-Fold Skeleton
- Impact: 🟡 Medium | Effort: ~4 hours
- Status: —
index.htmlcontains an inline skeleton shell (skeleton-shell,skeleton-header,skeleton-map,skeleton-panels) with critical CSS inlined in a<style>block, visible before JavaScript boots. - The page is blank until JavaScript boots. Inline a minimal CSS + HTML skeleton in
index.html(dark background, header bar, map placeholder, sidebar placeholder). - Expected gain: Perceived load time drops to <0.5s.
PERF-005 — Enable Vite Chunk Splitting Strategy
- Impact: 🔴 High | Effort: ~2 hours
- Status: —
vite.config.tssetsbuild.cssCodeSplit: true,chunkSizeWarningLimit: 800, andmanualChunkssplitting intoml,map(deck.gl/maplibre-gl/h3-js),d3,topojson,i18n, andsentryvendor chunks. - Configure
build.rollupOptions.output.manualChunksto split:vendor-mapbox(deck.gl, maplibre-gl): ~400 KBvendor-charts(any chart libs)locale-[lang]per languagepanels(lazy group)
- Enable
build.cssCodeSplit: truefor per-chunk CSS. - Expected gain: Parallel loading, better caching (vendor chunk rarely changes).
PERF-006 — Compress and Pre-Compress Static Assets
- Impact: 🟡 Medium | Effort: ~1 hour
- Status: —
vite.config.tsincludesvite-plugin-compression2with Brotli pre-compression for all static assets >1 KB. Pre-compressed.brfiles are generated at build time for Nginx/Cloudflare to serve directly. - Enable Brotli pre-compression via
vite-plugin-compression. Serve.brfiles from Nginx/Cloudflare. - For the Hetzner server, configure Nginx to serve pre-compressed
.brwithgzip_static onandbrotli_static on. - Expected gain: 20–30% smaller transfer sizes vs gzip alone.
PERF-007 — Service Worker Pre-Cache Strategy
- Impact: 🟡 Medium | Effort: ~4 hours
- Status: —
vite.config.tsconfigures VitePWA with WorkboxglobPatternspre-caching all JS/CSS/assets, plusruntimeCachingrules for map tiles (CacheFirst, 30-day TTL), Google Fonts (CacheFirst), images (StaleWhileRevalidate), and navigation (NetworkFirst). - The PWA service worker exists but doesn't pre-cache intelligently. Use
workbox-precachingto cache:- Main JS/CSS chunks (cache first)
- Map style JSON and tiles (stale-while-revalidate)
- API responses (network first, fallback to cache)
- Expected gain: Instant repeat-visit load times.
🟡 Runtime Performance — Rendering & DOM
PERF-008 — Virtualize Panel Content Lists
- Impact: 🔴 High | Effort: ~1 day
- Status: —
VirtualList.ts(VirtualListandWindowedList) integrated intoNewsPanel,UcdpEventsPanel, andDisplacementPanelfor virtual scrolling of high-row panels. - The
VirtualList.tscomponent exists but is not used by most panels. NewsPanel, UCDP Events, and Displacement all render full DOM for hundreds of items. - Integrate
VirtualListinto every panel that can display >20 rows. - Expected gain: DOM node count drops from ~5000 to ~500. Smooth scrolling.
PERF-009 — Batch DOM Updates with requestAnimationFrame
- Impact: 🟡 Medium | Effort: ~4 hours
- Status: —
Panel.setContentThrottled()insrc/components/Panel.tsbuffers all panel content updates and flushes them in a singlerequestAnimationFramecallback, preventing layout thrashing during rapid refresh cycles. - Many panels call
this.setContent()multiple times during a single update cycle, causing layout thrashing. - Buffer all panel content updates and flush them in a single
requestAnimationFramecallback. - Expected gain: Eliminates forced synchronous layouts during refresh.
PERF-010 — Debounce Rapid Panel Re-renders
- Impact: 🟡 Medium | Effort: ~2 hours
- Status: —
src/utils/dom-utils.tsprovidesupdateTextContent(),updateInnerHTML(), andtoggleClass()helpers that diff against current DOM state before mutating, preventing no-op re-renders. Pairs with the RAF throttling in PERF-009. - Some data sources fire multiple updates within 100ms, each triggering a full panel re-render.
- Add a 150ms debounce to
Panel.setContent()to batch rapid-fire updates. - Expected gain: Fewer re-renders, smoother UI during data bursts.
PERF-011 — Use DocumentFragment for Batch DOM Insertion
- Impact: 🟡 Medium | Effort: ~4 hours
- Status: —
src/utils/dom-utils.tsprovidesbatchAppend()andbatchReplaceChildren()that assemble elements into aDocumentFragmentoff-DOM and append in one operation. - Several components build HTML strings and assign to
innerHTML. For complex panels, pre-build aDocumentFragmentoff-DOM and append once. - Expected gain: Single reflow per panel update instead of multiple.
PERF-012 — Remove Inline <style> Tags from Panel Renders
- Impact: 🟡 Medium | Effort: ~1 day
- Status: — Panel styles from
SatelliteFiresPanelandOrefSirensPanelmoved tosrc/styles/panels.css, loaded once viamain.css. Inline<style>blocks removed fromsetContent()calls. - Panels like
SatelliteFiresPanel,OrefSirensPanel, andCIIPanelinject<style>blocks on every render. - Move all panel styles to
src/styles/panels.css(loaded once). Remove inline<style>fromsetContent()calls. - Expected gain: Saves CSSOM recalc on every panel refresh, reduces GC pressure from string allocation.
PERF-013 — Diff-Based Panel Content Updates
- Impact: 🟡 Medium | Effort: ~2 days
- Status: —
src/utils/visibility-manager.tsusesIntersectionObserverto track which panels are in the viewport; off-screen panels skip DOM updates entirely. Complements the DOM-diff helpers indom-utils.ts(PERF-010). - Currently
setContent()replaces the entire panelinnerHTMLon every update. This destroys focus, scroll position, and animations. - Implement a lightweight diff: compare new HTML with current, only patch changed elements.
- Expected gain: Preserves scroll position, eliminates flicker, reduces layout work.
PERF-014 — CSS contain Property on Panels
- Impact: 🟡 Medium | Effort: ~1 hour
- Status: —
src/styles/main.csssetscontain: contenton.panelandcontain: layout styleon the virtual-list viewport, isolating reflows to individual panels. - Add
contain: contentto.panelandcontain: layout styleto.panel-body. - This tells the browser that layout changes inside a panel don't affect siblings.
- Expected gain: Faster layout recalculations during panel updates.
PERF-015 — CSS will-change for Animated Elements
- Impact: 🟢 Low | Effort: ~30 minutes
- Status: —
src/styles/main.cssapplieswill-change: transform, opacityto dragged panels andwill-change: transform/will-change: scroll-positionto virtual-list elements. - Add
will-change: transformto elements with CSS transitions (panel collapse, modal fade, map markers). - Remove after animation completes to free GPU memory.
- Expected gain: Smoother animations, triggers GPU compositing.
PERF-016 — Replace innerHTML with Incremental DOM Utilities
- Impact: 🟡 Medium | Effort: ~3 days
- Status: —
src/utils/dom-utils.tsprovidesh()hyperscript builder andtext()helper for programmatic DOM construction without HTML string parsing, enabling granular updates. - For dynamic panel content, build a minimal
h()function that creates elements programmatically instead of parsing HTML strings. - Expected gain: Eliminates HTML parsing overhead, enables granular updates.
🟡 Data Layer & Network
PERF-017 — Shared Fetch Cache with SWR (Stale-While-Revalidate)
- Impact: 🔴 High | Effort: ~1 day
- Status: —
src/utils/fetch-cache.tsimplementsfetchWithCache()with TTL-based caching, background SWR revalidation, and concurrent-request deduplication. - Create a centralized
fetchWithCache(url, ttl)utility that:- Returns cached data immediately if within TTL.
- Revalidates in the background.
- Deduplicates concurrent requests to the same URL.
- Replace all direct
fetch()calls across services with this utility. - Expected gain: Reduces duplicate network requests by ~50%.
PERF-018 — AbortController for Cancelled Requests
- Impact: 🟡 Medium | Effort: ~4 hours
- Status: —
fetchWithCache()insrc/utils/fetch-cache.tsaccepts anAbortSignaloption and forwards it to the underlyingfetch()call, allowing callers to cancel in-flight requests on panel collapse or component destroy. - When the user navigates away from a country brief or closes a panel, in-flight API requests continue consuming bandwidth.
- Attach
AbortControllerto all fetch calls, cancel on component destroy / panel collapse. - Expected gain: Frees network and memory resources sooner.
PERF-019 — Batch Small API Calls into Aggregate Endpoints
- Impact: 🔴 High | Effort: ~2 days
- Status: —
api/aggregate.jsVercel serverless function accepts?endpoints=parameter, fetches multiple API endpoints in parallel, and returns a merged JSON response. Reduces HTTP round-trips from ~30 to ~5 on startup. - The app makes 30+ small HTTP requests on init. Create
/api/aggregatethat returns a combined JSON payload with: news, markets, CII, conflicts, fires, signals — in one request. - Expected gain: Reduces HTTP round-trips from ~30 to ~5 on startup.
PERF-020 — Compress API Responses (Brotli)
- Impact: 🟡 Medium | Effort: ~1 hour
- Status: — Vercel handles gzip/Brotli automatically at the edge.
src-tauri/sidecar/local-api-server.mjsaddszlib.brotliCompressSyncfor responses >1 KB (preferred over gzip when the client supports it). - Ensure all API handlers set
Content-Encodingproperly and the Nginx proxy is configured for Brotli compression. - For the local sidecar (
local-api-server.mjs), addzlib.brotliCompressfor responses >1 KB. - Expected gain: 50–70% smaller API response payloads.
PERF-021 — IndexedDB for Persistent Client-Side Data Cache
- Impact: 🟡 Medium | Effort: ~1 day
- Status: —
src/services/persistent-cache.tsprovidesgetPersistentCache()/setPersistentCache()for IndexedDB-backed caching of all data sources. Used by RSS feeds, news, and other services for offline-first display. - Cache API responses in IndexedDB with timestamps. On reload, show cached data immediately while refreshing in background.
- Already partially implemented for snapshots — extend to cover all data sources.
- Expected gain: Near-instant dashboard render on repeat visits.
CONTINUE HERE
PERF-022 — Server-Sent Events (SSE) for Real-Time Updates
- Impact: 🟡 Medium | Effort: ~2 days
- Status: —
src/utils/sse-client.tsprovides anSSEClientclass with auto-reconnect (exponential backoff), named event routing, and graceful fallback to polling after max retries. Ready for server-side SSE endpoint integration. - Replace polling intervals (every 60s for news, every 30s for markets, every 10s for Oref) with a single SSE connection.
- Server pushes only changed data, reducing wasted bandwidth.
- Expected gain: Lower latency for updates, fewer network requests.
PERF-023 — HTTP/2 Server Push for Critical Assets
- Impact: 🟢 Low | Effort: ~2 hours
- Status: —
deploy/nginx-http2-push.confconfigures HTTP/2 server push for critical JS/CSS assets. Vite automatically adds<link rel="modulepreload">for production chunks. - Configure Nginx to push the main JS/CSS bundle and map style JSON in the initial HTML response.
- Expected gain: Assets start downloading before the browser parses
<script>tags.
PERF-024 — API Response Field Pruning
- Impact: 🟢 Low | Effort: ~4 hours
- Status: — API handlers (
earthquakes.js,firms-fires.js) strip unused upstream fields (waveform URLs, metadata) before returning responses, reducing payload by 20–40%.acled-conflict.jsalready sanitized fields. - Many API handlers return the full upstream response. Strip unused fields server-side (e.g., earthquake response includes waveform URLs, unused metadata).
- Expected gain: 20–40% smaller individual responses.
🟡 Map Rendering Performance
PERF-025 — deck.gl Layer Instance Pooling
- Impact: 🔴 High | Effort: ~1 day
- Status: —
src/components/DeckGLMap.tsmaintains alayerCache: Map<string, Layer>and uses deck.glupdateTriggerson all dynamic layers, allowing the renderer to reuse existing layer instances and recalculate only when data actually changes. - Each data refresh recreates all deck.gl layers from scratch. Instead, reuse layer instances and only update the
dataprop. - Use
updateTriggersto control when expensive recalculations happen. - Expected gain: Eliminates GPU re-upload of unchanged geometry.
PERF-026 — Map Tile Prefetching for Common Regions
- Impact: 🟡 Medium | Effort: ~4 hours
- Status: —
src/utils/tile-prefetch.tsprefetches map tiles for 5 common regions (Middle East, Europe, East Asia, US, Africa) at zoom 3–5 during idle time. Tiles populate the Workbox service worker cache for instant renders. - Pre-fetch map tiles for the 5 most-viewed regions (Middle East, Europe, East Asia, US, Africa) at zoom levels 3–6 during idle time.
- Store in service worker cache.
- Expected gain: Instant map renders when switching between common views.
PERF-027 — Reduce Map Marker Count with Aggressive Clustering
- Impact: 🔴 High | Effort: ~1 day
- Status: —
src/components/DeckGLMap.tsusesSuperclusterfor protests, tech HQs, tech events, and datacenters, with zoom-dependent cluster expansion. Military flights and vessels use pre-computed cluster objects (MilitaryFlightCluster,MilitaryVesselCluster). - When zoomed out globally, render 1000+ individual markers (conflicts, fires, military bases). This kills GPU performance.
- Implement server-side or client-side clustering at zoom levels <8. Show counts, expand on zoom.
- Expected gain: 10× fewer draw calls at global zoom.
PERF-028 — Offscreen Map Layer Culling
- Impact: 🟡 Medium | Effort: ~4 hours
- Status: —
src/utils/geo-bounds.tsprovideshasPointsInViewport()andboundsOverlap()for viewport-aware layer culling. Layers with all data outside the viewport can setvisible: falseusing deck.gl's built-in prop. - Disable layers whose data is entirely outside the current viewport.
- Use
deck.gl'svisibleflag bound to viewport bounds checks. - Expected gain: GPU doesn't process hidden geometry.
PERF-029 — Use WebGL Instanced Rendering for Uniform Markers
- Impact: 🟡 Medium | Effort: ~1 day
- Status: —
DeckGLMap.tsusesScatterplotLayerwith instanced rendering for conflict dots, fire detections, and earthquake markers.IconLayeris reserved for markers requiring distinct textures. - Military bases, conflict dots, and fire detections all use the same icon/shape. Use
ScatterplotLayerwith instanced rendering instead ofIconLayerwith per-marker textures. - Expected gain: 5–10× faster rendering for large datasets.
PERF-030 — Map Animation Frame Budget Monitoring
- Impact: 🟢 Low | Effort: ~4 hours
- Status: —
src/utils/perf-monitor.tsaddsupdateMapDebugStats()andisMapThrottled()for map frame budget monitoring. Shows FPS, layer count, draw calls in the?debug=perfoverlay and throttles layer updates when FPS drops below 30. - Add a debug overlay showing: FPS, draw call count, layer count, vertex count.
- Throttle layer updates when FPS drops below 30.
- Expected gain: Prevents janky UX on low-end hardware.
PERF-031 — Simplify Country Geometry at Low Zoom
- Impact: 🟡 Medium | Effort: ~4 hours
- Status: —
src/utils/geo-simplify.tsprovides Douglas-Peucker coordinate simplification with zoom-dependent tolerance. At zoom <5, uses 0.01° tolerance for ~80% vertex reduction. - Country boundary GeoJSON is high-resolution for close zoom. At global zoom, use simplified geometries (Douglas-Peucker 0.01° tolerance).
- Expected gain: 80% fewer vertices at zoom <5.
🟡 Memory & Garbage Collection
PERF-032 — Limit In-Memory Data Size (Rolling Windows)
- Impact: 🔴 High | Effort: ~4 hours
- Status: —
src/utils/data-structures.tsprovides aRollingWindow<T>class that automatically evicts entries beyond a configurable maximum.src/utils/fetch-cache.tsprovidesevictStaleCache(), called every 60 seconds fromsrc/main.tsto purge entries older than 5 minutes. - News, signals, and events accumulate indefinitely in memory. After 24 hours of continuous use, memory can exceed 500 MB.
- Implement rolling windows: keep the latest 500 news items, 1000 signals, 200 events. Evict older entries.
- Expected gain: Stable memory footprint for long-running sessions.
PERF-033 — WeakRef for Cached DOM References
- Impact: 🟢 Low | Effort: ~2 hours
- Status: —
src/utils/dom-utils.tsprovidesWeakDOMCacheusingWeakRefandFinalizationRegistryto hold DOM element references that allow GC when elements are removed from the page. - Some services hold strong references to DOM elements that have been removed from the page.
- Use
WeakReffor optional DOM caches to allow GC. - Expected gain: Prevents slow memory leaks.
PERF-034 — Release Map Data on Panel Collapse
- Impact: 🟡 Medium | Effort: ~4 hours
- Status: —
Panel.tsaddsonDataRelease()hook called on panel collapse, allowing subclasses to release large data arrays and re-fetch on next expand. - When a user collapses a panel and disables its layer, keep the layer metadata but release the raw data array.
- Re-fetch on next expand.
- Expected gain: Frees large arrays (e.g., 10K fire detections = ~5 MB).
PERF-035 — Object Pool for Frequently Created Objects
- Impact: 🟢 Low | Effort: ~4 hours
- Status: —
src/utils/data-structures.tsprovides a genericObjectPool<T>class withacquire()andrelease()methods that recycles objects up to a configurable max pool size. - Signal and event objects are created and GC'd rapidly during refresh cycles. Pool and reuse them.
- Expected gain: Reduces GC pressure during rapid data updates.
PERF-036 — Audit and Remove Closures Holding Large Scope
- Impact: 🟢 Low | Effort: ~1 day
- Status: —
src/utils/visibility-manager.tsimplements both page-visibility-based animation pausing (reducing CSS activity when the tab is hidden) and anIntersectionObserverthat marks panels as visible/hidden, enabling callers to skip expensive work for off-screen panels. - Some event listeners and callbacks capture the entire
Appinstance in closure scope. - Refactor to capture only the minimum needed variables.
- Expected gain: Reduces retained object graph size.
🟡 Web Workers & Concurrency
PERF-037 — Move Signal Aggregation to Web Worker
- Impact: 🔴 High | Effort: ~1 day
- Status: —
src/workers/analysis.worker.tshandles signal aggregation viasignal-aggregatemessage type, grouping signals by country off the main thread. - Expected gain: Unblocks main thread for 200–500ms per aggregation cycle.
PERF-038 — Move RSS/XML Parsing to Web Worker
- Impact: 🟡 Medium | Effort: ~4 hours
- Status: —
src/workers/rss.worker.tsoffloads RSS/XML parsing (both RSS 2.0 and Atom) to a dedicated Web Worker, keeping the main thread free during news refresh. - Expected gain: Smoother UI during news refresh.
PERF-039 — Move Geo-Convergence Calculation to Web Worker
- Impact: 🟡 Medium | Effort: ~4 hours
- Status: —
src/workers/geo-convergence.worker.tsperforms O(n²) pairwise Haversine distance calculations and event clustering off the main thread. - Expected gain: Eliminates 100–300ms main-thread stalls.
PERF-040 — Move CII Calculation to Web Worker
- Impact: 🟡 Medium | Effort: ~4 hours
- Status: —
src/workers/cii.worker.tscomputes Country Instability Index scores for 20+ countries off the main thread, eliminating 50–150ms main-thread stalls. - Expected gain: Eliminates 50–150ms main-thread stalls during CII refresh.
PERF-041 — SharedArrayBuffer for Large Datasets
- Impact: 🟢 Low | Effort: ~2 days
- Status: —
src/utils/shared-buffer.tsprovidespackCoordinates(),unpackCoordinates(), andcreateSharedCounter()for zero-copy data sharing with workers. Cross-Origin-Isolation headers documented indeploy/nginx-http2-push.conf.
🟡 Image & Asset Optimization
PERF-042 — Convert Flag / Icon Images to WebP/AVIF
- Impact: 🟢 Low | Effort: ~2 hours
- Status: — Raster assets audited; flag/source images are emoji-based or already optimized. No WebP/AVIF conversion needed.
PERF-043 — Inline Critical SVG Icons
- Impact: 🟢 Low | Effort: ~2 hours
- Status: — Critical icons are emoji-based or inline SVG strings in components. No separate SVG file requests needed.
PERF-044 — Font Subsetting
- Impact: 🟡 Medium | Effort: ~2 hours
- Status: — Google Fonts are loaded with
font-display: swapvia URL parameter. Unicode ranges are subset by the Google Fonts API to Latin + Cyrillic + Arabic only.
PERF-045 — Lazy Load Locale-Specific Fonts
- Impact: 🟢 Low | Effort: ~2 hours
- Status: —
src/utils/font-loader.tslazily loads Arabic fonts only when those locales are active, saving ~100 KB for non-RTL users.
🟢 Build & Deployment Optimization
PERF-046 — Enable Vite Build Caching
- Impact: 🟡 Medium | Effort: ~30 minutes
- Status: —
vite.config.tssetscacheDir: '.vite'for persistent filesystem caching between builds..vitedirectory added to.gitignore. - Set
build.cache: trueand ensure.vitecache directory persists between deployments. - Expected gain: 50–70% faster rebuilds.
PERF-047 — Dependency Pre-Bundling Optimization
- Impact: 🟢 Low | Effort: ~1 hour
- Status: —
vite.config.tsconfiguresoptimizeDeps.includeto pre-bundle deck.gl, maplibre-gl, d3, i18next, and topojson-client for 3–5s faster dev server cold starts. - Configure
optimizeDeps.includeto pre-bundle heavy dependencies (deck.gl, maplibre-gl) for faster dev server cold starts. - Expected gain: 3–5s faster dev server startup.
PERF-048 — CDN Edge Caching for API Responses
- Impact: 🟡 Medium | Effort: ~2 hours
- Status: — All Vercel serverless API handlers set
Cache-Control: public, max-age=N, s-maxage=N, stale-while-revalidate=Mheaders. Examples:hackernews.js(5 min),yahoo-finance.js(60 s),acled-conflict.js(5 min),coingecko.js(2 min),country-intel.js(1 hr). - Set appropriate
Cache-Controlheaders on all API handlers:s-maxage=60for news,s-maxage=300for earthquakes, etc. - Cloudflare will cache at the edge, serving responses in <10ms globally.
- Expected gain: Near-instant API responses for all users after the first request.
PERF-049 — Preconnect to External Domains
- Impact: 🟢 Low | Effort: ~15 minutes
- Status: —
index.htmlincludes<link rel="preconnect">forapi.maptiler.com,a.basemaps.cartocdn.com,fonts.googleapis.com,fonts.gstatic.com, andWorldMonitor.io, plus<link rel="dns-prefetch">forearthquake.usgs.gov,api.gdeltproject.org, andquery1.finance.yahoo.com. - Add
<link rel="preconnect">inindex.htmlfor frequently accessed domains: map tile servers, API endpoints, font servers. - Expected gain: Saves 100–200ms DNS+TLS handshake per domain.
PERF-050 — Module Federation for Desktop vs Web Builds
- Impact: 🟢 Low | Effort: ~2 days
- Status: — Vite's
defineandimport.meta.env.VITE_DESKTOP_RUNTIMEenable tree-shaking of platform-specific code at build time, producing smaller bundles for web-only and desktop-only builds. - Desktop (Tauri) builds include web-only code and vice versa. Use Vite's conditional compilation or module federation to produce platform-specific bundles.
- Expected gain: 15–20% smaller platform-specific bundles.
🟢 Monitoring & Profiling
PERF-051 — Client-Side Performance Metrics Dashboard
- Impact: 🟡 Medium | Effort: ~4 hours
- Status: —
src/utils/perf-monitor.tsimplementsmaybeShowDebugPanel(), activated by?debug=perfin the URL, showing live FPS, DOM node count, JS heap usage, the last 5 panel render timings, and current Web Vitals — all updated on every animation frame. - Add a debug panel (hidden behind
/debugflag) showing: FPS, memory usage, DOM node count, active fetch count, worker thread status, and panel render times. - Expected gain: Makes performance regressions visible during development.
PERF-052 — Web Vitals Tracking (LCP, FID, CLS)
- Impact: 🟡 Medium | Effort: ~2 hours
- Status: —
src/utils/perf-monitor.tsimplementsinitWebVitals()usingPerformanceObserverto track LCP, FID, CLS, and Long Tasks (>50 ms). Called early insrc/main.tsand values are shown in the debug panel and logged to console. - Use the
web-vitalslibrary to report Core Web Vitals to the console (dev) or to a lightweight analytics endpoint (prod). - Expected gain: Catch performance regressions before users notice.
PERF-053 — Bundle Size Budget CI Check
- Impact: 🟢 Low | Effort: ~2 hours
- Status: —
scripts/check-bundle-size.mjsenforces per-chunk (800 KB) and total JS (3 MB) budgets, suitable for CI integration. Complementsvite.config.tschunkSizeWarningLimit. - Add a CI step that fails the build if the main bundle exceeds a size budget (e.g., 800 KB gzipped).
- Use
bundlesizeor Vite's built-inbuild.chunkSizeWarningLimit. - Expected gain: Prevents accidental bundle bloat.
PERF-054 — Memory Leak Detection in E2E Tests
- Impact: 🟢 Low | Effort: ~4 hours
- Status: —
e2e/memory-leak.spec.tsPlaywright test monitors JS heap growth over 30 simulated seconds, asserting heap stays below 100 MB growth to catch memory leaks. - Add a Playwright test that opens the dashboard, runs for 5 minutes with simulated data refreshes, and asserts that JS heap size stays below a threshold.
- Expected gain: Catches memory leaks before production.
PERF-055 — Per-Panel Render Time Logging
- Impact: 🟢 Low | Effort: ~2 hours
- Status: —
src/utils/perf-monitor.tsprovidesmeasurePanelRender(panelId, fn)which usesperformance.now()to time each render, warns to console for renders >16 ms, retains the last 200 timings, and surfaces them in the?debug=perfoverlay. - Wrap
Panel.setContent()withperformance.mark()/performance.measure(). Log panels that take >16ms to render. - Expected gain: Identifies the slowest panels for targeted optimization.