MDX Limo
Electron + Next.js Performance Audit and Optimization Plan

Electron + Next.js Performance Audit and Optimization Plan

Summary

  • Keep the current Electron + Next.js architecture and optimize within it.
  • The biggest current bottlenecks are: Electron waiting on a local standalone Next server and showing the window before first paint, a globally mounted client shell that always loads heavy agent/session state, request-time route rendering on top-level pages that should be cacheable, and large list views that fetch or render far more data than needed for the initial view.
  • Concrete repo findings: main.ts creates the main BrowserWindow immediately and does not use show: false + ready-to-show; app-shell-content.tsx mounts AgentProvider, ContentProvider, and related agent UI infrastructure for every route; keyword-explorer.ts builds the keywords view from several large server queries per render. /competitors explicitly calls connection() and also runs scheduleExternalSiteSync() during render, which should be removed from the render path.
  • Reliability issues are also affecting performance work: next build currently fails during page-data collection because of a Mastra module-resolution error, and browser runs on /library, /keywords, and /competitors show a background 500 from /api/sessions.

Implementation Changes

  1. Fix the blocking correctness issues first. Resolve the Mastra external-module failure so next build completes, and stop the unconditional /api/sessions request on non-agent routes. No performance baseline is trustworthy until build and idle-route errors are clean.
  2. Improve Electron startup and perceived boot time. Keep the embedded standalone Next server, but change the main window to show: false, set a background color, wait for ready-to-show, and only then reveal the window. Add startup timings for app.ready, Next ready, first load, ready-to-show, and background services ready. Defer nonessential work until after first paint: workspace population/sync, file watcher startup, scheduler startup, and any optional runtime checks.
  3. Split the shared shell so the agent stack is not paid on every page. Move the heavy agent runtime behind user intent: only mount the full agent/session provider when the dock is opened or the agent page is active. Keep the sidebar/header shell lightweight. Context panels can stay lazy, but agent session history and session polling must not initialize on content pages.
  4. Refactor top-level content routes to a “fast shell + streamed/cached data” model. /keywords, /competitors, /strategy, and /content/dashboard should render a static or cached shell immediately, then stream or fetch their expensive sections. Remove connection() from any route that does not truly require request-time rendering. Where runtime data is needed, isolate it under narrow Suspense boundaries instead of making the whole route dynamic.
  5. Apply Next 16 cache components consistently. The app already has cacheComponents: true and /library uses use cache; extend that pattern to read-model helpers for keywords, competitors, dashboard summaries, and strategy workspace data. Use explicit cacheLife(...) on every cached loader, and use cacheTag plus revalidateTag/updateTag for mutation paths instead of broad request-time recomputation. Because this desktop app is self-hosted inside a long-lived Electron process, the in-memory runtime cache should materially improve repeat page switches.
  6. Remove side effects from route render paths. scheduleExternalSiteSync() must move out of the /competitors page render path into an explicit action, idle/background job, or mutation-triggered flow. Route renders and layouts should stay pure so prefetching does not trigger unnecessary work.
  7. Reduce initial client work and hydration cost. Run pnpm next experimental-analyze once the build is fixed, trim whatever lands in the shared shell bundle, and move noninteractive transforms back to Server Components where possible. Keep using dynamic imports for optional panels, but also lazy-load the agent dock/session surfaces. Do not add optimizePackageImports for lucide-react; Next already optimizes it by default.
  8. Change heavy list pages to load only what the user can see. /library should stop sending and rendering the entire merged content/reference set into one client table; implement server-paginated slices for the table plus row virtualization for the visible list. /keywords and /competitors already paginate in the client after loading the full dataset; change them to fetch cached summaries plus paginated row slices, with filters/search driving slice requests rather than full-route recomputation.
  9. Improve navigation warming deliberately. Keep next/link for sidebar navigation, add route-local loading.tsx or tight nested Suspense fallbacks for heavy destinations, and prefetch only the high-value sidebar routes (/library, /competitors, /keywords, /strategy) on hover or idle if bundle pressure stays acceptable. Do not rely on coarse route-group loading alone.

Public APIs / Interfaces

  • Keep all current page URLs unchanged.
  • Add internal paginated read-model interfaces for library, keywords, and competitors so the initial page render only needs summary data plus the first slice.
  • Add internal startup/navigation telemetry events for Electron and route loaders so regressions can be measured instead of guessed.
  • Keep current mutation routes, but switch cache invalidation from broad route refresh behavior to tagged cache invalidation where the data model allows it.

Test Plan

  • Establish baselines for packaged desktop cold start, first meaningful paint, warm navigation between /library, /competitors, /keywords, and /strategy, and JS heap after visiting the three heaviest routes.
  • Verify next build succeeds and produces analyzable output before and after the refactor.
  • Use Next DevTools route/error metadata plus Playwright navigation checks to confirm top-level routes render without background 500s and with route-level loading feedback.
  • Acceptance criteria: no idle 500s on non-agent pages, the main window appears only after first paint, warm sidebar switches hit cached/streamed shells immediately, and list-heavy routes no longer fetch or render the full dataset for the initial view.

Assumptions

  • Stay on Electron; no Tauri or native-shell migration in this pass.
  • Prioritize page-switch speed and perceived responsiveness over a full architectural rewrite.
  • Treat the Mastra build failure and /api/sessions background error as P0 prerequisites because they block reliable measurement.
  • Use Next 16 cache components as the primary route-speed mechanism, since this app already enables them and the embedded Node server can retain runtime cache across navigations.

References