· Muhammad Azamuddin

Preparing Productive Electron Codebase

This is not tutorial. This is a minimal note of the electron architecture that I use.

Core Architecture

  • Electron React Boilerplate (Perhaps I’d migrate to electron-vite next, get things done is more important for now)
  • (via electron-trpc)
  • for backend (if require internet API), deployed to Cloudflare Worker
  • Xstate
  • @tanstack/react-query then integrated with trpc


  • Server code (
  • App code (electron)
    • Main processes
    • Renderer processes

For each of those parts, we communicate over trpc.

  • Server code expose TrpcRouter AppRouter to be consumed by main process (Utils.trpcInternet) and renderer process utils/trpc-internet-react
  • Main process also expose TrpcRouter MainRouter to be consumed by renderer processes utils/trpc-main

Possible communication:

  • Main -> Internet Server (Cloudflare) via trpcInternet
  • Renderer -> Internet Server (Cloudflare) via trpcInternetReact
  • Renderer -> Main via trpcMain


Main or Renderer process can have their own machine, where renderer machine will only responsible for UI related state and Main machine will be responsible the whole app state / global state.

To communicate from Renderer UI to Main Machine, we will use Trpc.

Possible communication:

  • Renderer -> trpcMain dispatch event -> Main Machine
  • Main -> direct dispatch -> Main Machine
  • Renderer -> direct dispatch -> Renderer Machine

What about Main to Renderer Machine? (Use standard window.webContents.send), and renderer listen to dispatch to renderer machine.

  • We use standard IPC communication, window.webContents.send and then on preload we handle it ipcRenderer.on that also can be accessed in UI
    • We can setup mechanism to sync Main Machine state inside Renderer process, such that it can be accessed directly.

Other important packages

  • Zod
  • @xstate/immer


When implementing trpcInternet we got error, No fetch implementation found. This because node has no fetch implementation, to solve it we use package electron-fetch and and replace internal trpc implementation with fetch from electron-fetch

Why we consider to migrate to electron-vite

  • It is newer (this can be good or bad at the same time)
  • After initial trying, it works smoothly
  • Typescript auto import works better especially in renderer process
  • Configuration seems simpler and well documented
  • Folder structure is minimal and preload is separated
  • I think we have better control over package bundler config (I suspect, the ESM ERR REQUIRE can be solved trivially in electron-vite)