Mo Sharif
Back to writing

PWA vs Electron: Making a Web App Feel at Home

In this article

The request was straightforward: give Codelit a dedicated window so architecture work does not disappear among browser tabs.

I chose a PWA because the product already lived on the web. That solved distribution without requiring a desktop package. It did not, by itself, solve offline work, updates, or the details that make an installed app dependable.

A progressive web app

uses web platform capabilities to offer app-like behavior, including installation and offline support where the product and browser implement them.

I chose a PWA because Codelit's core interaction was already a web canvas and a dedicated window met the immediate need. Packaging a desktop runtime would have added release responsibilities without resolving a product requirement I could not meet in the browser.

That is a fit decision, not a claim that Electron is wrong for solo developers. Electron can share application code with a website, and divergence is not inevitable.

Its costs are real but variable: packaged runtime size, platform builds, signing, native integration, and updates. Electron supports several update approaches, including hosted services; a custom update server is not universally required.

Keeping Codelit's main application in one web delivery path was the trade-off I wanted at the time.

Installability depends on browser and platform support, a suitable manifest for promotion, and a secure deployment. A service worker is useful for offline behavior but is not universally required for installation. Passing a checklist does not guarantee an install prompt appears.

MDN's installability guide separates those requirements and documents platform differences. The manifest should define a stable identity, start URL, display mode, and suitable icons.

Test icon cropping, launch routing, and account state from the installed window. An app that opens the marketing page after every launch technically installs, but does not feel like a workspace.

A standalone window removes familiar browser navigation, so the application must make location, back navigation, settings, and recovery easy to find. Installation exposes weaknesses that were previously masked by tabs, the address bar, and browser controls.

Codelit's command palette helps people navigate and run actions from the keyboard. It should complement visible controls, not become the only route through the product.

Check deep links and external links too. A user should understand whether they are still in the app, opening a website, or returning from authentication. Test the actual installed context rather than assuming it behaves exactly like a tab.

Cache content according to its sensitivity, freshness requirements, and offline value. Public versioned assets are straightforward candidates. User-specific API responses require much more care, including account isolation, invalidation, and logout cleanup.

ContentStarting policyImportant limit
Versioned static assetsCache by versionRemove obsolete versions deliberately
Public images and fontsBounded cacheURLs must change when immutable content changes
Public reference contentStale content may be acceptableLabel freshness when it matters
Authenticated account dataNetwork-only unless explicitly designed otherwiseAvoid cross-account or post-logout exposure
User documentsExplicit local persistence and syncHandle conflicts and failed writes
AI generation and billingNetwork requestDo not replay a cached response as a new operation

I would avoid blanket API caching. A convenient offline fallback can become a privacy bug or display stale entitlements if it ignores which account the response belongs to.

Choose a service-worker integration that supports your actual Next.js version and build pipeline. Adding a package from an old tutorial is not evidence that its caching rules or generated worker are correct for the current application.

Offline support requires more than loading the interface from cache. The user's data must be available locally, edits must survive reload, and synchronization must handle reconnects without silently losing work. State those guarantees precisely in the interface.

An offline checklist should include an already-open document, a full reload, a new edit, closing and reopening the app, and reconnecting after another client changed the same document.

AI generation still needs its network dependencies. An editable canvas does not imply the current diagram was saved locally or synchronized remotely.

Use browser connectivity events as hints and actual request outcomes as evidence. A device can have a network connection while the API is unreachable, and a browser's offline heuristic can also be wrong. Keep errors specific to the work that failed.

MDN warns that the online indicator is inherently unreliable. It is useful for a banner, not as the sole reason to disable every network feature.

A failed save should say the save failed. A provider timeout should say generation could not complete. Neither should erase local work or leave a spinner with no exit.

Installation UI should match the browser's actual capabilities. Show a custom install action only when its supporting event is available, and provide platform-appropriate instructions elsewhere. Keep ordinary web use fully available when installation is unsupported or declined.

The browser-controlled install event should be used from a user action, not triggered repeatedly. Dismissal is a valid choice.

On iOS, use the Share menu's Add to Home Screen route. On macOS Safari, Add to Dock is another path. Browser features evolve, so test the devices you support and avoid an evergreen promise that every visitor will see the same icon.

The installed-state media query describes the current window's display mode. It is not a universal inventory of every installation on the device.

Service-worker updates can wait while an older version still controls open pages. Cached resources and unsaved data make immediate activation a compatibility decision, not an automatic improvement. A reliable app tells users when a safe reload is available.

The service-worker lifecycle explains the waiting and activation stages. Forcing activation without coordinating the page can mix old application code with new worker behavior.

Test an update with two windows open and unsaved work in one of them. Keep assets available long enough for older clients, and version local data migrations. A fresh incognito load misses these failure paths entirely.

Profile the installed app and the browser tab under the same workload before assigning a cause. Similar code can run under different window, device, and resource conditions, but a performance difference does not prove the browser disabled GPU acceleration.

The React Flow canvas deserves representative pan, zoom, selection, and large-graph tests. Inspect rendering and memory before adding compositor hints everywhere.

MDN describes will-change as a last-resort optimization, not a guaranteed layer-promotion switch. Applying it to every node can consume resources and make performance worse.

Electron becomes attractive when deeper operating-system integration is central to the product: native menus, tray behavior, controlled runtime behavior, or file workflows beyond the browser support you can rely on. Compare those requirements against its release and security responsibilities.

DecisionPWAElectron
RuntimeUser's browser engineBundled application runtime
DeliveryWeb deployment plus cache lifecyclePlatform packages plus update lifecycle
Native integrationBrowser APIs and permissionsBroader desktop APIs
Offline behaviorMust be designedMust also be designed
Shared web codeNatural fitPossible with deliberate boundaries
VerificationBrowser and installed-context matrixPlatform and packaged-app matrix

The useful result is not an install badge. It is a workspace that opens where the user expects, preserves their work, and tells the truth when it cannot complete an operation. That is what makes the web app feel at home.

Questions people actually ask

What is a progressive web app?
A progressive web app uses web capabilities to provide an app-like experience, including installation where supported. A manifest describes its identity and launch behavior. A service worker can support offline behavior, but installation and offline capability are separate features with browser-specific requirements.
Should I build a PWA or an Electron app?
Choose based on the operating-system capabilities and distribution model the product needs. A PWA can reuse an existing browser-based application with less packaging work. Electron adds a bundled runtime and deeper native integration, with separate packaging, signing, security, and update responsibilities.
Does an installed PWA automatically work offline?
No. Installation does not persist the user's documents or make network APIs available offline. The application needs deliberate caching, local storage, synchronization, and conflict handling. Verify reload and editing while offline before promising that work will remain available.
How do users install a web app on an iPhone?
Supported iPhone browsers offer an Add to Home Screen path through the Share menu. The custom beforeinstallprompt flow is not supported on iOS. Provide instructions matched to the browser and avoid showing a button that assumes an install event will arrive.
Does every PWA update immediately after deployment?
No. Open tabs and service-worker caches can continue using an older version. Design an update lifecycle that keeps assets and data compatible, protects unsaved work, and makes a waiting update understandable. Test returning users, not only a fresh browser visit.