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
BrowserWindowimmediately and does not useshow: false+ready-to-show; app-shell-content.tsx mountsAgentProvider,ContentProvider, and related agent UI infrastructure for every route; keyword-explorer.ts builds the keywords view from several large server queries per render./competitorsexplicitly callsconnection()and also runsscheduleExternalSiteSync()during render, which should be removed from the render path. - Reliability issues are also affecting performance work:
next buildcurrently fails during page-data collection because of a Mastra module-resolution error, and browser runs on/library,/keywords, and/competitorsshow a background500from/api/sessions.
Implementation Changes
- Fix the blocking correctness issues first. Resolve the Mastra external-module failure so
next buildcompletes, and stop the unconditional/api/sessionsrequest on non-agent routes. No performance baseline is trustworthy until build and idle-route errors are clean. - 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 forready-to-show, and only then reveal the window. Add startup timings forapp.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. - 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.
- Refactor top-level content routes to a “fast shell + streamed/cached data” model.
/keywords,/competitors,/strategy, and/content/dashboardshould render a static or cached shell immediately, then stream or fetch their expensive sections. Removeconnection()from any route that does not truly require request-time rendering. Where runtime data is needed, isolate it under narrowSuspenseboundaries instead of making the whole route dynamic. - Apply Next 16 cache components consistently. The app already has
cacheComponents: trueand/libraryusesuse cache; extend that pattern to read-model helpers for keywords, competitors, dashboard summaries, and strategy workspace data. Use explicitcacheLife(...)on every cached loader, and usecacheTagplusrevalidateTag/updateTagfor 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. - Remove side effects from route render paths.
scheduleExternalSiteSync()must move out of the/competitorspage 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. - Reduce initial client work and hydration cost. Run
pnpm next experimental-analyzeonce 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 addoptimizePackageImportsforlucide-react; Next already optimizes it by default. - Change heavy list pages to load only what the user can see.
/libraryshould 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./keywordsand/competitorsalready 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. - Improve navigation warming deliberately. Keep
next/linkfor sidebar navigation, add route-localloading.tsxor tight nestedSuspensefallbacks 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 buildsucceeds 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/sessionsbackground 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