DEV Community

RAXXO Studios
RAXXO Studios

Posted on • Originally published at raxxo.shop

How to Build and Distribute an Electron Desktop App in 2026

  • Electron powers 8,000+ Mac App Store apps; cold start times now under 500ms with modern versions.

  • Electron architecture requires three processes: main (Node.js backend), renderer (Chromium frontend), preload (secure bridge).

  • Never disable context isolation or enable nodeIntegration; use preload scripts to safely expose main process APIs.

  • electron-builder packages apps for macOS (DMG), Windows (NSIS), and Linux; include darkModeSupport for menu bar apps.

  • Apple code signing costs 99 EUR annually; critical decision for macOS distribution.

Electron Is Still Here. Deal With It.

Every year, someone declares Electron dead. Every year, more Electron apps ship. VS Code, Slack, Discord, Figma (desktop), Notion, 1Password 8 - the list keeps growing. As of early 2026, Electron powers over 8,000 apps on the Mac App Store alone, and npm downloads for electron-builder average 2.1 million per week.

The "Electron is bloated" argument had merit in 2018. In 2026, with Electron 34+ shipping Chromium 132 and Node 22, cold start times are under 500ms for well-built apps. That's faster than most native Swift apps that phone home on launch.

I built OhNine - a Claude usage meter that lives in your menu bar - with Electron. This post covers everything I learned: architecture, build config, the code signing decision, and distribution.

Project Architecture: Main, Renderer, Preload

Every Electron app has three processes. Getting the boundaries right is the most important architectural decision you'll make.

Main Process

This is your Node.js backend. It creates windows, handles system-level APIs (tray icons, notifications, file system), and manages the app lifecycle. For OhNine, the main process handles:

  • Creating the tray icon and menu

  • Polling Claude's usage endpoint on an interval

  • Storing settings in electron-store

  • Managing the popup window position relative to the tray icon

Renderer Process

This is your frontend - HTML, CSS, JavaScript running in a Chromium window. It has no direct access to Node.js APIs (and shouldn't). For OhNine, the renderer is a lightweight HTML/CSS interface that displays usage data. No React, no framework - just vanilla JS.

Preload Script

The bridge between main and renderer. It selectively exposes main process functionality to the renderer using contextBridge.exposeInMainWorld(). This is where security lives. Never skip the preload script. Never set nodeIntegration: true. A large share of Electron apps with critical vulnerabilities have disabled context isolation or enabled node integration in the renderer.

// preload.js - expose only what the renderer needs
const { contextBridge, ipcRenderer } = require('electron');

contextBridge.exposeInMainWorld('api', {
  getUsage: () => ipcRenderer.invoke('get-usage'),
  onUsageUpdate: (callback) => ipcRenderer.on('usage-update', callback),
  openSettings: () => ipcRenderer.send('open-settings'),
});
Enter fullscreen mode Exit fullscreen mode

Build Configuration with electron-builder

electron-builder is the standard for packaging Electron apps. It handles DMG creation (macOS), NSIS installers (Windows), AppImage/deb/rpm (Linux), and auto-update infrastructure. Here's a real config from OhNine:

The build config handles DMG for macOS, NSIS for Windows, and AppImage for Linux. I use electron-builder for all packaging.

Key settings people miss: darkModeSupport: true on macOS (essential for menu bar apps), a proper appId in reverse-domain format, and explicit icon paths for each platform.

The 99 EUR Apple Code Signing Decision

This is where most indie developers get stuck. Apple requires a Developer ID certificate to sign macOS apps. That certificate requires an Apple Developer Program membership at 99 EUR per year. Without it, users see a scary "unidentified developer" warning.

Here's the honest breakdown:

  Factor
  Signed (99 EUR/year)
  Unsigned (free)




  First-launch experience
  Clean, no warnings
  "Unidentified developer" dialog


  User workaround
  None needed
  Right-click > Open (one time)


  Gatekeeper
  Passes automatically
  Blocked by default


  Notarization
  Required and possible
  Not possible


  Mac App Store
  Eligible
  Not eligible


  Auto-updates
  Seamless
  Works, but may trigger warnings


  Trust signal
  Professional
  Looks sketchy to non-devs
Enter fullscreen mode Exit fullscreen mode

For OhNine, Distribution is handled directly through the website. The audience is developers who know how to right-click and open. The install instructions explain the one-time workaround clearly. Based on download data, the unsigned warning has not measurably impacted conversion - but we're selling to a technical audience. If your target market includes non-developers, pay the 99 EUR.

Auto-Updates

electron-updater (part of electron-builder) supports auto-updates via GitHub Releases, S3, or a custom server. The flow:

  • App checks for updates on launch and periodically

  • If a new version exists, it downloads in the background

  • User gets notified and can restart to apply

For small apps distributed outside the Mac App Store, GitHub Releases is the simplest backend. Push a new tag, GitHub Actions builds the artifacts, electron-updater finds them automatically. Total infrastructure cost: 0 EUR.

One gotcha: auto-updates on macOS require code signing. Unsigned apps can check for updates and notify the user, but can't silently replace their own binary. The user has to manually download and replace. Most Electron apps on macOS use signed auto-updates.

Distribution Channels

Where you distribute depends on your audience and your signing situation:

  • Your own website: Full control, no commission, works with or without signing. Direct DMG/EXE/AppImage download.

  • GitHub Releases: Free hosting, version history, integrates with auto-updater. Best for open source or developer-focused apps.

  • Mac App Store: Requires signing + sandboxing + Apple review. 30% commission. Massive discovery but strict rules.

  • Homebrew Cask: Popular for developer tools on macOS. Submit a PR to homebrew-cask with your download URL. Free, community-maintained.

  • Shopify (for paid apps): Sell the download as a digital product. This is what I do with OhNine - 9 EUR on raxxo.shop, delivered as a download link after purchase.

Performance Tips from Shipping OhNine

Menu bar apps are uniquely demanding because users expect them to be invisible. Zero CPU when idle, instant popup, minimal memory. Here's what I optimized:

  • Lazy window creation: Don't create the popup window on app launch. Create it on first tray click. Saves 50-80ms of startup time.

  • Hide instead of close: When the user clicks away, hide the window instead of destroying it. Recreation is expensive; showing a hidden window is instant.

  • Throttle polling: OhNine checks usage every 30 seconds, not continuously.

  • Skip frameworks: React/Vue/Svelte add 100-300KB to your bundle and initialization time. For simple UIs, vanilla HTML/CSS/JS is faster and lighter.

The final binary: 89MB for the DMG (mostly Chromium), 45MB memory usage at idle, 0.0% CPU when not polling. Those numbers are competitive with native menu bar apps written in Swift.

Common Mistakes to Avoid

  • Enabling nodeIntegration in the renderer. This is the biggest security mistake in Electron development. Always use the preload script pattern.

  • Bundling devDependencies. electron-builder should only include production dependencies. Check your package.json - a misconfigured build can double your app size.

  • Ignoring the dock icon on macOS. Menu bar apps should set app.dock.hide() so they don't appear in the dock. Forgetting this makes your tray app look broken.

  • Not testing on Windows. Electron is cross-platform, but window positioning, tray behavior, and file paths all differ between macOS and Windows. Test on both.

  • Over-engineering the first version. Ship a working app first. Add auto-updates, code signing, and App Store distribution later. OhNine v1.0 shipped without auto-updates and it was fine.

FAQ

Should I use Electron or Tauri in 2026?

Tauri produces smaller binaries (5-10MB vs 80-100MB) and uses less memory. But Electron has a larger ecosystem, better documentation, and more battle-tested tooling. If binary size is critical, go Tauri. If development speed and ecosystem matter more, go Electron. For menu bar apps specifically, Electron's tray API is more mature.

How do I handle macOS permissions (camera, microphone, etc.)?

Add the required permission strings to your Info.plist via electron-builder's extendInfo config. macOS will prompt the user automatically. For menu bar apps that don't need special permissions (like OhNine), you can skip this entirely.

What's the minimum viable Electron app structure?

Three files: main.js (creates window), preload.js (bridges APIs), index.html (your UI). Plus package.json with electron and electron-builder as dependencies. You can have a working, distributable app in under 100 lines of code total.

Is it worth paying for code signing just for one app?

If your app targets non-technical users, yes - the unsigned warning will kill your conversion rate. If you're selling to developers (like I am with OhNine), you can ship unsigned and include clear install instructions. The 99 EUR per year becomes worth it once you have multiple apps or a non-technical audience.

Can I use TypeScript with Electron?

Yes. Use ts-node for development and compile to JS for production. electron-builder works with compiled JS output. Many large Electron apps (including VS Code) are written in TypeScript. The setup adds about 10 minutes to your initial project configuration.

This article contains affiliate links. If you sign up through them, I earn a small commission at no extra cost to you.

Top comments (0)