Desktop apps,
built with web tools.
One React codebase, three platforms — Windows, macOS, and Linux. Signed installers, auto-updates, and native integrations where needed. Below is a real project structure from a production Electron app.
export function createWindow() {
const win = new BrowserWindow({
width: 1280,
height: 800,
titleBarStyle: 'hiddenInset',
webPreferences: {
preload: path.join(__dirname, '../preload/bridge.js'),
contextIsolation: true, // secure by default
nodeIntegration: false, // renderer can't touch Node
sandbox: true, // OS-level sandbox
},
});
win.loadURL(process.env.VITE_DEV_SERVER_URL
?? 'app://index.html');
return win;
}3
Platforms from one codebase
~120 MB
Typical installed size
60fps
Chromium rendering
5–10 d
Engineer placement
The stack
Tools we ship with.
Electron 31+
Runtime
electron-vite
Build tooling
electron-builder
Packaging
Forge
Alternative packaging
TypeScript
End to end
React
Renderer layer
Zod
IPC validation
electron-updater
Auto-updates
Playwright
E2E tests
Vitest
Unit tests
Sentry
Crash reporting
Notarize
Apple signing
Security posture
Secure by default. Not bolted on later.
Almost every Electron app that gets compromised skipped one of these. Every project we ship starts with them enforced in the scaffold.
contextIsolation: true
criticalIsolated context for preload scripts. Non-negotiable.
nodeIntegration: false
criticalRenderer can't touch Node directly. Always off.
sandbox: true
criticalOS-level sandbox for every renderer. Default on.
webSecurity: true
criticalSame-origin policy enforced. No exceptions.
CSP on every window
criticalContent Security Policy headers set. Blocks injection attacks.
No remote module
criticalDeprecated and dangerous. Never enabled.
Whitelisted IPC channels
criticalEvery channel explicitly declared. No wildcards.
Typed IPC payloads
Zod schemas validate every message. Both sides.
Minimal preload surface
Expose only what the renderer needs. Nothing more.
No remote content in privileged windows
External URLs go to the OS browser or a sandboxed view.
will-navigate locked down
Navigation handlers block unexpected URLs.
Signed updates verified
Auto-update payloads checked against signature before install.
If any of these are missing, your app is at risk
Electron's default settings are safe — but frameworks, tutorials, and legacy code often disable them for convenience. We enforce them at the scaffold level so they can't be turned off by accident.
The process model
Three processes. One app.
Electron isn't one process — it's a small system of them. Understanding the split is the difference between a clean app and a fragile one.
Node.js
Main process
- App lifecycle
- Native APIs (dialogs, tray, menus)
- IPC handlers
- Auto-updater
- Global shortcuts
Chromium
Renderer process
- Your React/Vue/Svelte app
- Sandboxed by default
- No Node access
- One per window
- Isolated context
Node.js (sandboxed)
Utility process
- Heavy compute
- File parsing
- Background work
- Image processing
- Isolated from UI
Typed IPC
Every channel typed. Every payload validated.
IPC is where Electron apps get messy. Channels named by string literals, payloads with no shape, errors that arrive as mystery objects. We don't do that.
- Renderer can't reach Node directly — preload is the only bridge
- Every channel has a name, a schema, and a handler
- Payloads validated with Zod on both sides
- Errors structured, not thrown as strings
- TypeScript types shared across all three processes
- No `send('do-anything', ...)` patterns anywhere
// packages/shared/schemas.ts
import { z } from 'zod';
export const InvoiceSchema = z.object({
customerId: z.string().uuid(),
amount: z.number().positive(),
currency: z.enum(['GHS', 'USD', 'EUR']),
});
// main/ipc.ts
ipcMain.handle('invoice:create', async (_e, input) => {
const data = InvoiceSchema.parse(input); // runtime validation
return invoiceService.create(data);
});
// preload/bridge.ts
contextBridge.exposeInMainWorld('api', {
invoices: {
create: (data) => ipcRenderer.invoke('invoice:create', data),
},
});
// renderer/App.tsx — typed, safe, validated
await window.api.invoices.create({
customerId: '...',
amount: 500,
currency: 'GHS',
});Reference architecture
What a production Electron project looks like.
Click any file to see its role and the actual code we ship. Same structure as our internal starter template.
repository
why this file exists
Entry point. Creates the main window, registers IPC handlers, handles app lifecycle.
Honest comparison
Electron vs Tauri vs NativePHP.
We build all three. The right choice depends on your team, your stack, and what you're optimizing for.
Electron
Best fitNode backend · JS-first
Bundle
~120 MB
Backend
Node.js
Learning
Zero — if you know JS
- JavaScript/TypeScript-first team
- Largest ecosystem of native modules
- Existing web codebase to share
- Bundle size not the top constraint
- Broadest community and tooling
Tauri
Rust backend · smallest bundle
Bundle
~10 MB
Backend
Rust
Learning
Steep — Rust required
- Bundle size is critical
- Performance is the top concern
- Team comfortable with Rust
- Tightest security surface matters
- Modern architecture is a priority
NativePHP
PHP backend · Laravel-native
Bundle
~120 MB
Backend
PHP (Laravel)
Learning
Zero — if you know PHP
- Existing Laravel app to leverage
- Team writes PHP, not JS/Rust
- Desktop companion to a web product
- One language across the stack
- You want to stay in the Laravel ecosystem
Bundle size
What the installer actually weighs.
Electron bundles Node and Chromium — same footprint class as NativePHP. Tauri is an order of magnitude smaller. For enterprise internal tools, it rarely matters. For consumer apps downloaded on mobile data, it does.
Renderer-agnostic
Bring whatever you already use.
The renderer is a Chromium window. React, Vue, Svelte, vanilla — if it renders in Chrome, it runs here. Usually your existing web app runs with minimal changes.
React
Largest ecosystem, best tooling
Vue
Lower ceremony, great DX
Svelte
Smallest runtime, fastest startup
Vanilla + Vite
Zero framework tax
Your existing app
Same codebase as your web
Any Chromium target
If it renders in Chrome, it runs here
Performance
Fast cold start. Small memory. Real numbers.
Electron gets a bad rap for bloat. The fixes are well-known — we apply them by default.
V8 snapshot
Pre-compile JavaScript for 30–50% faster cold start.
ASAR packing
Files bundled into a single archive — faster access, tamper-resistant.
Lazy window creation
Don't open all windows at launch. Create on demand.
Renderer pooling
Reuse renderer processes instead of recreating them.
Chromium flags
Disable unused features — geolocation, Bluetooth, sensors.
Startup budgets
Cold start < 2s, warm start < 500ms, window ready < 1s.
< 2s
Cold start target
< 500ms
Warm start
60–100 MB
Base memory
40–80 MB
Per renderer window
Native modules
When JavaScript can't reach.
Most Electron apps never need native code. When they do — a specific USB device, an unusual codec, hardware integration — we write it.
N-API / node-addon-api
Modern C++ addon interface. ABI-stable across Node versions.
Rust via napi-rs
Modern, memory-safe native addons. Best of both worlds.
Prebuilt binaries
Ship without forcing users to compile. Faster installs, fewer failures.
@electron/rebuild
Auto-rebuild native modules against Electron's Node version.
Testing
Modern tests. Real coverage.
Spectron is dead. We use the modern Electron testing stack — Playwright for E2E, Vitest for main process units, and a CI matrix across all three platforms.
Playwright
Official Electron support. End-to-end tests, screenshots, video.
Vitest
Main process unit tests. Fast, minimal config, TypeScript native.
WebdriverIO
Alternative E2E framework with broad Electron support.
GitHub Actions matrix
CI on Windows, macOS, and Linux in parallel.
Signing in CI
Automated code signing and notarization on release.
Visual regression
Playwright snapshots catch pixel-level UI regressions.
Distribution & signing
Signed on all three platforms.
Unsigned installers get blocked by users. Every app we ship is properly signed, notarized, and distributed with an auto-update feed.
Windows
- 01Authenticode certificate from a CA
- 02Sign .exe and NSIS installer
- 03Build SmartScreen reputation over time
- 04Ship as NSIS installer, MSI, or Squirrel
macOS
- 01Apple Developer ID certificate
- 02Enable hardened runtime
- 03Notarize with Apple's notary service
- 04Staple ticket, distribute as .dmg or .zip
Linux
- 01AppImage for portable distribution
- 02.deb for Debian/Ubuntu
- 03.rpm for Fedora/RHEL
- 04Snap and Flatpak for sandboxed installs
Auto-update flow
Release to installed in five steps.
Release
New version published to signed release feed.
Check
Installed apps poll the feed on schedule.
Download
Delta download where possible — smaller payload.
Verify
Signature verified before install. Rejects tampering.
Install
Runs on next launch, or forced if critical.
Release channels
Four tracks, one feed.
Stable
Production users. Tested, signed, released.
Beta
Opt-in testers. New features, rough edges okay.
Canary
Nightly builds. Bleeding edge, may break.
Enterprise LTS
Pinned major version with security backports for 18 months.
Real-world use
What teams actually build.
Developer tools
IDEs, terminals, database clients, API explorers. The VS Code class of app.
Communication apps
Slack-class chat, voice, and collaboration tools. WebRTC, notifications, tray.
Design & media
Figma-class design tools, video editors, audio workstations. Canvas, GPU, media codecs.
Enterprise desktop
Internal admin tools for private networks. Deployed via MDM, GPO, or SCCM.
POS & kiosk
Retail terminals, self-service stations. Hardware integration, locked-down mode.
Desktop companions
Desktop app to a SaaS product. Shared codebase, one team, one deployment.
In the box
What ships with the app.
Node.js runtime
Bundled Node — user installs nothing separately.
Chromium + V8
Full Chromium engine with the V8 JavaScript runtime.
Your app (ASAR packed)
All your code packaged into a single .asar archive.
Preload script
The bridge between renderer and main. Minimal, typed.
Auto-updater
Signed release feed with delta downloads.
Native modules
Any C++/Rust addons your app needs, prebuilt.
The process
Five phases. Every Electron build follows them.
Discovery
Platform targets, security model, native integrations, and shared-code strategy. Architecture locked before any code.
Deliverables
Platform matrix · Security model · Feature scope · Fixed-price quote
Shell & security
Project scaffolded with contextIsolation, sandbox, typed IPC, and a strict preload bridge. Secure by default.
Deliverables
Project scaffold · Typed IPC · Preload bridge · CSP policy
Build
Main process, renderer, and shared packages in parallel. Weekly builds to your testers on all three platforms.
Deliverables
Working builds · Weekly demos · Documentation · Preview channel
Signing & packaging
Authenticode, Developer ID, AppImage/deb/rpm. Installers tested on clean machines. Update flow validated.
Deliverables
Signed installers · Notarization · Update feed · Install report
Launch & handover
Release pipeline live, crash reporting wired, auto-updates flowing. Your team walks away with everything.
Deliverables
Production release · Crash reporting · Update pipeline · Full IP
Timelines
How long an Electron build takes.
Simple
6–8 wk
- Single-platform launch
- Standard windows and menus
- Basic security posture
- One renderer target
Feature-rich
10–14 wk
- Windows + macOS + Linux
- Complex UI and native integrations
- Auto-update pipeline
- Full security hardening
Complex
16–20 wk
- Multi-window, tray, global shortcuts
- Native modules (C++/Rust)
- Enterprise deployment (MDM/SCCM)
- Custom auto-update channels
Honest advice
When Electron fits.
When it doesn't.
Choose Electron when
- Your team is JavaScript/TypeScript-first
- You already have a web codebase to share
- Bundle size isn't the top constraint
- You need the broadest native module ecosystem
- Community size and long-term support matter
Consider alternatives when
- Bundle size is critical (Tauri is 10× smaller)
- Your team is Rust-first and wants the tightest surface
- You have a Laravel app (NativePHP is a better fit)
- You need mobile from the same codebase
- Performance-critical native UI is the main use case
Selected work
Electron apps in production.
Cross-platform IDE
A code editor for a niche language, shipped on all three platforms with a built-in terminal, debugger, and extension marketplace.
Outcome
3 platforms, 1 codebase
Internal admin for a healthcare network
Desktop admin app deployed across 400 workstations via MDM. Private network, no browser, hardware badge reader.
Outcome
400 deployments
Desktop app for a project management platform
Offline-capable desktop companion sharing 65% of code with the web app. Notifications, global shortcuts, tray.
Outcome
65% shared code
Client feedback
What our clients
actually say.
“The move to SikaNet CBS was a bigger shift than I expected — but the right one. Before, our Susu collectors were on paper and every BOG report took us days to assemble. Now collections appear in the office before a collector even finishes their route, and the reports are just there. The team understood our business from day one.”
David Awuni
Founder, Adwumapa Microfinance
“We were cautious about moving patient records online. The on-prem approach changed that — everything stays inside the health center, nothing goes to a foreign cloud. Our clinicians now retrieve a file in seconds instead of ten minutes, and every entry has an audit trail behind it.”
Medical Director
Sekyedumase Health Center
“The patient management system they developed has streamlined our ophthalmology department. Appointment scheduling, patient records, and referral tracking are now seamless — saving us hours of administrative work every day.”
Isaac Adu
Ophthalmologist, Ghana Health Service
Let us talk
Building for desktop?
We can help — whether you need a full team or just one engineer to get you to launch.
