본문 바로가기

Capacitor와 Electron으로 데스크톱 앱 빌드하기 (2026)

Capacitor 앱을 Electron 기반 데스크톱 앱으로 전환하기: 보안 셸 설정, 플러그인 전략, macOS/Windows/Linux 패키징, 라이브 업데이트.

Capacitor 프로젝트에서 Electron으로 데스크톱 앱을 빌드하는 가이드용 Capgo 배너

Capacitor 앱에서 데스크톱 앱을 빌드하려면, 동일한 웹 빌드(webDir)를 Electron 셸로 감싸면 됩니다: 앱을 여는 메인 프로세스와 BrowserWindow, 작은 데스크톱 API를 노출하는 preload script, 그리고 macOS, Windows, Linux용 설치 프로그램을 만드는 electron-builder를 사용합니다. UI와 비즈니스 로직은 iOS, Android와 계속 공유하고, 데스크톱 전용 기능은 Electron main process에 있습니다.

이 가이드는 2026년 기준 Electron에서의 Capacitor 현황, Capacitor 8과 호환되는 보안 설정, 플러그인 동작 방식, 패키징 및 서명, 그리고 새 설치 프로그램 없이 웹 업데이트를 배포하는 방법을 다룹니다.

2026년 Electron에서의 Capacitor 현황

수년 동안 표준 답변은 @capacitor-community/electron였으며, 이는 Electron을 Capacitor CLI 플랫폼에 연결하는 방식이었습니다. 이 프로젝트는 이제 자체 README에서 유지보수 중단으로 표시됩니다. 마지막 npm 릴리스인 5.0.1은 2023년 9월에 출시됐고, Capacitor 5.4 이상이 필요하며 Electron 26을 기준으로 빌드됐습니다. 그 이후 Electron은 여러 메이저 버전을 출시했고, 최신 3개 메이저 버전만 보안 수정을 받습니다.

기존 프로젝트에서는 여전히 사용할 수 있지만, Capacitor 8 앱을 기준으로 비교하면 다음과 같습니다:

접근 방식 장점 단점
자체 Electron shell (이 가이드) 최신 Electron, 완전한 제어, 추가 추상화 없음 main과 preload code를 약 60줄 작성
@capacitor-community/electron 익숙한 cap 명령어 유지보수 중단, 구버전 Capacitor 및 Electron에 고정됨

Shell 방식은 생각보다 간단합니다. Capacitor가 이미 정적 웹 빌드를 생성하기 때문에, Electron은 이를 로드하기만 하면 됩니다.

Electron에서 플러그인 동작 방식

Electron의 렌더러는 Chromium이므로, Capacitor 앱은 브라우저에서처럼 실행됩니다. Capacitor 코어는 네이티브 브릿지가 없음을 감지하고 각 플러그인의 웹 구현으로 폴백합니다.

플러그인 유형 Electron 지원 여부 해결 방법
웹 구현이 있는 플러그인 (Preferences, Share, Clipboard, 다수의 Capgo 플러그인) 네, 웹 버전 사용 웹 동작 테스트
네이티브 전용 플러그인 (웹 구현 없음) 아니요, 미구현으로 거부 preload API 뒤에 Electron 대응 구현을 작성하세요
순수 JS 라이브러리 지원 없음

Capacitor.getPlatform() 반환합니다 'web' 이 설정에서 Capacitor.isNativePlatform() 반환합니다 false. preload 스크립트에서 명시적인 데스크톱 플래그를 노출하여 code가 깔끔하게 분기 처리할 수 있도록 합니다.

사전 요구사항

  • 다음과 같은 Capacitor 앱의 bun run build 정적 폴더를 출력합니다 ( webDir 에서 capacitor.config.ts, 종종 dist 또는 www).
  • Bun 및 현재 LTS Node.js가 필요합니다. Electron 툴링은 여전히 Node에서 실행되기 때문입니다.
  • macOS 배포를 위한 Apple Developer ID 인증서가 필요합니다. Windows의 경우 code 서명 인증서가 필요합니다.

1단계: Electron과 packager 설치

bun add -d electron electron-builder

다음을 추가하세요: electron/ 프로젝트 루트의 폴더:

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

2단계: 웹 빌드가 디스크에서 로드되도록 설정

Electron은 프로덕션에서 파일을 로드할 때 file:// 프로토콜을 사용합니다. 두 가지 설정이 중요합니다:

  1. 상대 경로 자산. Vite에서 base: './' 을(를) 설정하여 빌드된 index.html 이(가) 참조하도록 ./assets/... 를 전달하고 /assets/....
  2. 라우팅. 다음과 같은 딥 링크 /settings 파일로 존재하지 않습니다. 데스크톱 빌드에는 해시 라우팅을 사용하거나, 로드 후 클라이언트 사이드에서만 라우터가 내비게이션하도록 하세요. index.html.
// vite.config.ts
import { defineConfig } from 'vite';

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

상대 경로도 iOS와 Android에서 작동하므로 별도의 빌드가 필요하지 않습니다.

3단계: 메인 프로세스 작성

// 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();
});

유지하세요 contextIsolation: true, nodeIntegration: false, 그리고 sandbox: true. 웹 앱이 서드파티 스크립트를 로드할 수 있으며, 어떤 스크립트도 Node.js에 접근할 수 없어야 합니다.

4단계: 프리로드에서 작은 데스크톱 API 노출

// 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>,
});

웹 code에 입력하고, 플러그인을 사용하는 것과 동일한 방식으로 사용하세요:

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

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

데스크톱에서 필요한 네이티브 전용 기능은 각각 하나씩 ipcMain.handle 메인 프로세스에 하나, 프리로드에 하나의 함수로 구현합니다. 렌더러는 신뢰할 수 없으므로 메인 프로세스에서 인수를 검증하세요.

5단계: 빌드 및 실행

Electron 파일을 Bun로 컴파일합니다. 프리로드는 샌드박스에서 실행되므로 단일 CommonJS 파일로 번들링하세요:

{
  "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"
  }
}

개발 중에는 터미널 하나에서 dev 서버를 시작하고 (bun run dev) 및 bun run electron:dev 다른 곳에서. 데스크톱 창에서 핫 리로드를 사용할 수 있습니다.

6단계: electron-builder로 설치 패키지 생성

# 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

실행 bun run electron:pack. electron-builder는 실행 중인 OS에 맞춰 빌드합니다. 세 가지를 모두 만들려면 CI에서 macOS, Windows, Linux 러너 매트릭스를 사용하세요.

OS 일반적인 타깃 서명
macOS가 .dmg, .zip Developer ID 인증서와 notarization이 없으면 Gatekeeper가 앱을 차단합니다
Windows NSIS .exe, MSI Code 서명 인증서로 SmartScreen 경고 방지
Linux AppImage, .deb, .rpm 선택 사항

Electron 앱은 Chromium과 Node.js를 포함하므로 모바일 빌드보다 용량이 큽니다. 타겟에 따라 설치 파일 크기는 약 80~150MB 정도입니다.

7단계: 새 설치 파일 없이 웹 업데이트 배포하기

Capacitor 앱의 변경 사항은 대부분 웹 변경입니다. 모바일에서는 이를 Capgo Live Update 사용하기로 배포할 수 있습니다. 데스크톱에서는 @capgo/electron-updater 채널, 내장 번들, 다운로드한 번들, 그리고 새 번들이 정상 작동하지 않을 때의 자동 롤백 등 동일한 모델로 같은 역할을 합니다. notifyAppReady().

bun add @capgo/electron-updater

업데이터가 로드할 번들을 선택할 수 있도록 메인 프로세스를 업데이트하세요:

// 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());

같은 preload 파일에 updater bridge를 추가하세요:

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

exposeUpdaterAPI();

그리고 데스크톱에서만 웹 앱에서 정상적으로 실행되는지 확인하세요:

import { isDesktop } from './desktop';

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

모바일에서 쓰는 것과 동일한 CLI로 새 번들을 업로드하세요:

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

웹 업데이트로는 Electron 자체나 메인 프로세스, preload를 변경할 수 없습니다. 이 경우에는 바이너리 auto-updater가 포함된 새 설치 파일을 배포해야 합니다. 관련 가이드는 Electron 자동 업데이트는 해당 경로를 다루고, electron-updater 플러그인 페이지에서는 및 문서 모든 옵션을 다룹니다.

문제 해결

패키지된 앱에서 빈 창이 표시됩니다. 에셋이 절대 경로를 사용합니다. 설정을 base: './' 하고 다시 빌드하세요. 패키지된 앱에서 DevTools를 열어 404 오류를 확인하세요. file:///assets/....

새로고침 후 라우트에서 빈 페이지가 표시됩니다. 라우터가 디스크에서 /route 를 로드하려고 했습니다. 데스크톱에서는 해시 라우팅으로 전환하세요.

require is not defined 또는 프리로드 오류가 발생합니다. 샌드박스된 프리로드는 런타임에 임의의 패키지를 로드할 수 없습니다. 위의 컴파일 스크립트처럼 단일 파일로 번들링하세요.

플러그인 호출이 “not implemented on web”으로 거부됩니다. 해당 플러그인에 웹 구현이 없습니다. 메인 프로세스에 IPC 핸들러를 추가하고 window.desktop.

macOS에서 앱이 손상되었거나 출처를 알 수 없는 개발자의 앱이라고 표시됩니다. 앱에 서명 및 노타리제이션이 적용되지 않았습니다. electron-builder에서 Developer ID 인증서와 노타리제이션을 구성하세요.

설치 직후 업데이트가 롤백됩니다. notifyAppReady() 타임아웃(기본 10초) 내에 호출되지 않았습니다. 앱 시작 시 초기에 호출하세요.

마무리

데스크톱에 도달하려면 전용 Capacitor 플랫폼 패키지가 필요하지 않습니다. 짧은 Electron main process, 타입이 지정된 API를 노출하는 preload, 상대 경로 자산, 그리고 electron-builder만 있으면 동일한 웹 빌드로 세 가지 운영체제용 설치 파일을 만들 수 있습니다. 추가 @capgo/electron-updater 을 추가하면 데스크톱 앱이 iOS 및 Android 빌드와 동일한 live update 워크플로우를 사용할 수 있습니다. 데스크톱 런타임 선택 중이라면 우리의 Electron vs Tauri 비교.

Capacitor 앱용 Live updates

웹 레이어 버그가 라이브 상태라면, 앱 스토어 승인을 며칠씩 기다리지 말고 Capgo를 통해 수정 사항을 배포하세요. 네이티브 변경 사항은 정상 리뷰 절차를 그대로 따르는 반면, 사용자는 백그라운드에서 업데이트를 받습니다.

블로그 최신 글

Capgo는 정말 프로페셔널한 모바일 앱을 만드는 데 필요한 최고의 인사이트를 제공합니다.