Zum Hauptinhalt springen

Desktop-App bauen mit Capacitor und Electron (2026)

Verwandle deine Capacitor App mit Electron in eine Desktop-App: sicheres Shell-Setup, Plugin-Strategie, Packaging für macOS, Windows, Linux und Live Updates.

Capgo-Banner für einen Leitfaden zum Erstellen einer Desktop-App aus einem Capacitor-Projekt mit Electron

Um eine Desktop-App aus einer Capacitor-App zu erstellen, kapseln Sie denselben Web-Build (webDir) in einer Electron-Shell: ein Hauptprozess, der eine BrowserWindow, ein Preload-Script, das ein kleines Desktop-API bereitstellt, und electron-builder, um Installer für macOS, Windows und Linux zu bauen. Deine UI und Business-Logik bleiben mit iOS und Android geteilt, Desktop-only-Features leben im Electron-Main-Prozess.

Dieser Guide behandelt den aktuellen Stand von Capacitor auf Electron im Jahr 2026, ein sicheres Setup, das mit Capacitor 8 funktioniert, wie sich Plugins verhalten, Packaging und Signing und wie du Web-Updates ohne neue Installer ausliefern kannst.

Der Stand von Capacitor auf Electron im Jahr 2026

Jahrelang war die Standardantwort @capacitor-community/electron, das Electron als Plattform in das Capacitor CLI integrierte. Dieses Projekt ist inzwischen im eigenen README als unmaintained markiert. Das letzte npm Release, 5.0.1, erschien im September 2023, setzt Capacitor 5.4 oder neuer voraus und wurde gegen Electron 26 gebaut. Seitdem sind viele Electron-Major-Versionen erschienen, und nur die letzten drei Majors bekommen Security-Fixes.

In alten Projekten kannst du es noch nutzen, aber für eine Capacitor 8 App sieht der Vergleich so aus:

Ansatz Vorteile Nachteile
Deine eigene Electron-Shell (dieser Guide) Aktuelles Electron, volle Kontrolle, keine extra Abstraktion Du schreibst etwa 60 Zeilen Main- und Preload-code
@capacitor-community/electron vertraut cap Befehle Nicht mehr gepflegt, an alte Capacitor- und Electron-Versionen gebunden

Der Shell-Ansatz ist weniger Arbeit, als es klingt, da Capacitor bereits einen statischen Web-Build erzeugt. Electron muss ihn nur laden.

Wie sich Plugins in Electron verhalten

Der Renderer von Electron ist Chromium, daher läuft deine Capacitor-App dort wie im Browser. Der Core von Capacitor erkennt keine native Bridge und fällt auf die Web-Implementierung jedes Plugins zurück.

Plugin-Typ Funktioniert in Electron? Was zu tun ist
Plugin mit Web-Implementierung (Preferences, Share, Clipboard, viele Capgo-Plugins) Ja, über die Web-Version Web-Verhalten testen
Nur natives Plugin (keine Web-Implementierung) Nein, wird als unimplementiert abgelehnt Schreibe ein Electron-Äquivalent hinter einem Preload-API
Reine JS-Bibliotheken Ja Nichts

Capacitor.getPlatform() gibt zurück 'web' in diesem Setup und Capacitor.isNativePlatform() gibt zurück false. Wir stellen einen expliziten Desktop-Flag aus dem Preload-Skript bereit, damit dein code sauber verzweigen kann.

Voraussetzungen

  • Eine Capacitor-App, deren bun run build ein statisches Ordner (die webDir in einem capacitor.config.ts, oft dist oder www).
  • Bun und eine aktuelle LTS-Version von Node.js, da die Electron-Toolchain weiterhin auf Node läuft.
  • Für die macOS-Verteilung ein Apple Developer ID-Zertifikat. Für Windows ein code-Signierzertifikat.

Schritt 1: Electron und den Packager installieren

bun add -d electron electron-builder

Füge ein electron/ Ordner im Projektstamm:

my-app/
  capacitor.config.ts
  dist/              # web build, shared with iOS and Android
  electron/
    main.ts
    preload.ts
  electron-builder.yml

Schritt 2: Den Web-Build von der Festplatte laden lassen

Electron lädt Dateien mit dem file:// Protokoll in der Produktion. Zwei Einstellungen sind wichtig:

  1. Relative Asset-Pfade. Mit Vite setzt du base: './' damit die gebaute Datei index.html referenziert ./assets/... anstatt /assets/....
  2. Routing. Deep Links wie /settings existieren nicht als Dateien. Nutze Hash-Routing für den Desktop-Build oder stelle sicher, dass dein Router nach dem Laden nur clientseitig navigiert. index.html.
// vite.config.ts
import { defineConfig } from 'vite';

export default defineConfig({
  base: './',
});

Relative Pfade funktionieren auch auf iOS und Android, du brauchst also keinen separaten Build.

Schritt 3: Den Main-Prozess schreiben

// electron/main.ts
import { app, BrowserWindow, ipcMain, shell } from 'electron';
import path from 'node:path';

const isDev = !app.isPackaged;

async function createWindow() {
  const win = new BrowserWindow({
    width: 1280,
    height: 800,
    minWidth: 800,
    minHeight: 600,
    show: false,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
      contextIsolation: true,
      nodeIntegration: false,
      sandbox: true,
    },
  });

  win.once('ready-to-show', () => win.show());

  // Open external links in the default browser, never inside the app.
  // Only allow https: so a script cannot launch file: or other protocol handlers.
  win.webContents.setWindowOpenHandler(({ url }) => {
    try {
      if (new URL(url).protocol === 'https:') void shell.openExternal(url).catch(() => {});
    } catch {
      // invalid URL: ignore
    }
    return { action: 'deny' };
  });

  if (isDev) {
    await win.loadURL('http://localhost:5173');
    win.webContents.openDevTools({ mode: 'detach' });
  } else {
    await win.loadFile(path.join(__dirname, '..', '..', 'dist', 'index.html'));
  }
}

ipcMain.handle('desktop:get-version', () => app.getVersion());

app.whenReady().then(createWindow);

app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') app.quit();
});

app.on('activate', () => {
  if (BrowserWindow.getAllWindows().length === 0) createWindow();
});

Behalten contextIsolation: true, nodeIntegration: false, und sandbox: true. Deine Web-App lädt eventuell Third-Party-Skripte, und keines davon sollte Zugriff auf Node.js bekommen.

Schritt 4: Eine kleine Desktop-API aus dem Preload bereitstellen

// electron/preload.ts
import { contextBridge, ipcRenderer } from 'electron';

contextBridge.exposeInMainWorld('desktop', {
  isElectron: true,
  platform: process.platform,
  getVersion: () => ipcRenderer.invoke('desktop:get-version') as Promise<string>,
});

Gib es einfach in deiner Web code ein und nutze es genau wie ein Plugin:

// src/desktop.ts
declare global {
  interface Window {
    desktop?: {
      isElectron: boolean;
      platform: string;
      getVersion: () => Promise<string>;
    };
  }
}

export const isDesktop = () => Boolean(window.desktop?.isElectron);

Jedes rein native Feature, das du auf dem Desktop brauchst, wird zu einer ipcMain.handle im Main-Prozess und eine Funktion im Preload-Skript. Validierung der Argumente sollte im Main-Prozess erfolgen, da der Renderer nicht als vertrauenswürdig gilt.

Schritt 5: Build und ausführen

Kompiliere die Electron-Dateien mit Bun. Das Preload-Script läuft sandboxed, also bundle es als einzelne CommonJS-Datei:

{
  "main": "electron/dist/main.js",
  "scripts": {
    "build": "vite build",
    "electron:compile": "bun build electron/main.ts --outdir electron/dist --target node --format cjs --external electron && bun build electron/preload.ts --outdir electron/dist --target node --format cjs --external electron",
    "electron:dev": "bun run electron:compile && electron .",
    "electron:pack": "bun run build && bun run electron:compile && electron-builder"
  }
}

Während der Entwicklung startest du deinen Dev-Server in einem Terminal (bun run dev) und bun run electron:dev in einem anderen. Du bekommst Hot Reload im Desktop-Fenster.

Schritt 6: Installer mit electron-builder packen

# electron-builder.yml
appId: com.example.myapp
productName: My App
directories:
  output: release
files:
  - dist/**/*
  - electron/dist/**/*
  - package.json
mac:
  target: [dmg, zip]
  category: public.app-category.productivity
  hardenedRuntime: true
win:
  target: [nsis]
linux:
  target: [AppImage, deb]
  category: Utility

Ausführen bun run electron:packelectron-builder baut für das OS, auf dem es läuft. Nutze eine Matrix aus macOS-, Windows- und Linux-Runnern in der CI, um alle drei zu erstellen.

OS Gängige Targets Signierung
macOS .dmg, .zip Developer-ID-Zertifikat plus Notarisierung, sonst blockiert Gatekeeper die App
Windows NSIS .exe, MSI Code Signing-Zertifikat, um SmartScreen-Warnungen zu vermeiden
Linux AppImage, .deb, .rpm Optional

Electron-Apps sind größer als mobile Builds, da sie Chromium und Node.js enthalten. Erwarte Installer zwischen 80 und 150 MB, abhängig von den Zielen.

Schritt 7: Web-Updates ohne neue Installer ausrollen

Die meisten Änderungen in einer Capacitor-App sind Web-Änderungen. Auf Mobile kannst du diese mit Capgo Live Updatesausliefern. Auf Desktop @capgo/electron-updater erledigt dieselbe Aufgabe mit demselben Modell: Kanäle, ein eingebettetes Bundle, heruntergeladene Bundles und automatisches Rollback, falls das neue Bundle nicht aufruft notifyAppReady().

bun add @capgo/electron-updater

Aktualisiere den Main Process, damit der Updater den zu ladenden Bundle auswählt:

// electron/main.ts (production branch)
import { ElectronUpdater, setupIPCHandlers, setupEventForwarding } from '@capgo/electron-updater';

const updater = new ElectronUpdater({
  appId: 'com.example.myapp',
  autoUpdate: true,
});

// inside createWindow(), instead of loading dist/index.html directly
const builtinPath = path.join(__dirname, '..', '..', 'dist', 'index.html');
await updater.initialize(win, builtinPath);
setupIPCHandlers(updater);
setupEventForwarding(updater, win);
await win.loadFile(updater.getCurrentBundlePath());

Füge die Updater-Bridge in dieselbe Preload-Datei ein:

// electron/preload.ts (next to the desktop API)
import { exposeUpdaterAPI } from '@capgo/electron-updater/preload';

exposeUpdaterAPI();

Und bestätige einen erfolgreichen Start aus der Web-App, nur auf Desktop:

import { isDesktop } from './desktop';

if (isDesktop()) {
  const { requireUpdater } = await import('@capgo/electron-updater/renderer');
  await requireUpdater().notifyAppReady();
}

Lade einen neuen Bundle mit demselben CLI hoch, den du für Mobile nutzt:

bun run build
bunx @capgo/cli@latest bundle upload --channel=production

Web-Updates können Electron selbst, den Main Process oder die Preload-Datei nicht ändern. Für diese Komponenten liefere einen neuen Installer mit einem Binary-Auto-Updater aus. Unser Guide zu Electron-Auto-Updates deckt diesen Weg ab, und die electron-updater-Plugin-Seite und Docs jede Option abdecken.

Fehlerbehebung

Leeres Fenster in der gepackten App. Assets verwenden absolute Pfade. Setze base: './' und baue neu. Öffne DevTools in der gepackten App, um 404s auf file:///assets/....

Routen zeigen nach dem Reload eine leere Seite. Der Router versuchte zu laden /route von der Festplatte. Wechsle für Desktop auf Hash-Routing.

require is not defined oder Preload-Fehler. Der sandboxed Preload kann zur Laufzeit keine beliebigen Packages laden. Bündle ihn in eine Datei, wie im Compile-Script oben.

Ein Plugin-Aufruf wird mit „not implemented on web“ abgelehnt. Dieses Plugin hat keine Web-Implementierung. Füge einen IPC-Handler im Main-Prozess hinzu und rufe ihn so auf: window.desktop.

macOS meldet, dass die App beschädigt ist oder von einem unbekannten Entwickler stammt. Die App ist nicht signiert und notarisiert. Konfiguriere ein Developer-ID-Zertifikat und die Notarisierung in electron-builder.

Updates rollen direkt nach der Installation zurück. notifyAppReady() wurde nicht innerhalb des Timeouts aufgerufen (standardmäßig 10 Sekunden). Rufe es früh beim App-Startup auf.

Fazit

Du brauchst kein eigenes Capacitor Plattform-Package für den Desktop. Ein kurzer Electron-Main-Prozess, ein Preload-Skript, das ein typisiertes API bereitstellt, relative Asset-Pfade und electron-builder liefern dir Installer für alle drei Betriebssysteme aus demselben Web-Build. Füge hinzu: @capgo/electron-updater und die Desktop-App bekommt denselben live update Workflow wie deine iOS- und Android-Builds. Falls du noch eine Desktop-Runtime suchst, lies unseren Vergleich von Electron und Tauri.

Live-Updates für Capacitor-Apps

Wenn ein Bug im Web-Layer live ist, liefere den Fix über Capgo aus, statt Tage auf die App-Store-Freigabe zu warten. Nutzer bekommen das Update im Hintergrund, während native Änderungen im normalen Review-Prozess bleiben.

Neues aus unserem Blog

Capgo gibt dir die besten Insights, um eine wirklich professionelle Mobile App zu erstellen.