Programming

Electron과 React로 데스크톱 애플리케이션 개발하기 (1)

슈도걸 2025. 4. 7. 23:39

먼저, Electron을 사용하는 이유

 

웹을 개발하면서도 한 번쯤 이런 궁금증을 품게 됩니다.

"이거 그냥 데스크탑 앱으로 만들 수는 없을까?"

 

Electron은 이런 요구에 맞아 떨어지는 대안입니다. 웹 기술로 데스크탑 앱을 만들 수 있으면서도, Native 수준의 파일 접근이나 시스템 API 제어도 가능합니다. 여기에 React까지 더하면, 웹 화면같은 익숙한 느낌이 들지만 데스크탑에서도 구동되는 UI 를 구성할 수 있습니다. Slack, Discord는 실제로 이렇게 Electron으로 개발한 대표적인 사례입니다.

 

 

 

Electron은 데스크탑 애플리케이션을 웹 기술로 개발할 수 있게 해주는 프레임워크로, Chromium과 Node.js를 결합한 구조를 가집니다. React는 프론트엔드 라이브러리로 이미 많은 프로젝트에서 검증된 도구입니다. 이 둘을 함께 사용하는 데는 몇 가지 구조적 고려사항이 필요합니다.

이 문서에서는 Electron과 React를 연동하여 데스크탑 애플리케이션을 구축하는 기본적인 구조 설계, 개발 환경 세팅, IPC 통신 구조 설계 등을 중심으로 설명합니다. 

 


Electron + React의 아키텍처 개요

처음 Electron을 도입할 때 가장 자주 마주치는 혼란은 "어디에서 무엇을 해야 하는가?" 입니다. Node.js와 Chromium이 함께 돌아가는 구조이기 때문에, 역할을 제대로 나누지 않으면 앱이 점점 복잡해지고 보안 리스크도 함께 커집니다.

 

프로세스 분리의 중요성

Electron은 아래처럼 크게 세 개의 실행 컨텍스트로 구성됩니다:

  1. Main Process
    • Node.js 환경에서 돌아갑니다.
    • 앱 창 생성, 파일 시스템 접근, OS-level API 접근을 담당합니다.
  2. Renderer Process
    • Chromium 환경, 즉 브라우저입니다.
    • 우리가 작성하는 React 기반 UI가 이곳에서 실행됩니다.
  3. Preload Script
    • Main과 Renderer 사이를 연결하는 안전한 브릿지입니다.
//프로젝트 구조
├─ main/         # Electron main process (Node.js)
├─ renderer/     # React 앱 (Renderer process)
├─ preload/      # Node 기능을 Renderer에 안전하게 expose

 

이 구조는 보안과 책임 분리를 동시에 달성하기 위한 설계입니다. 특히 Preload 스크립트를 통해 Renderer에 Node.js API를 무작정 노출하지 않도록 제한하는 것이 중요합니다.


개발 환경 세팅하기

이제 위 구조를 실제 프로젝트에 구현해 봅니다. 여기서 중요한 것은 각 실행 환경에 따라 개발 도구와 빌드 계획을 따로 가져가야 한다는 점입니다.

 

왜 별도 번들링이 필요한가?

Electron은 Node.js를 사용하는 반면, React는 브라우저에서 돌아가야 하므로, Webpack이나 Vite 등 번들러 설정을 분리해서 구성하는 것이 현실적인 선택입니다. 특히 Electron의 main과 preload는 target: 'electron-main' 또는 target: 'node'로, React는 target: 'web'로 설정해야 하죠.

npm install electron webpack typescript ...
npm install react react-dom ...

이 설정이 바로 React + Electron을 “함께” 쓴다기보다 “각자의 환경에서 따로” 쓰면서, 그 둘을 안전하게 연결하는 방향임을 보여줍니다.


React와 Electron의 통신 구조 설계 (IPC)

React에서 Electron의 기능을 사용하고 싶을 때, 가장 흔히 하는 실수는 다음과 같습니다:

// ❌ 위험한 방식
import fs from 'fs'; // Renderer에서 직접 Node 모듈 사용

이렇게 하면 보안상 매우 취약한 앱이 됩니다. 그래서 Electron은 preload.ts에서 명시적으로 안전한 API만 노출할 수 있도록 contextBridge를 제공합니다.

 

왜 preload가 중요한가?

Preload는 Main과 Renderer의 경계선입니다. 모든 시스템 리소스 접근은 Main을 통해 수행하고, 그 결과만 Renderer에 전달합니다. 이렇게 하면 XSS 공격에 의해 시스템 파일이 유출될 가능성을 사전에 차단할 수 있습니다.

// preload.ts
contextBridge.exposeInMainWorld('api', {
  getVersion: () => ipcRenderer.invoke('get-version')
});

 

// main.ts
ipcMain.handle('get-version', () => app.getVersion());

 

// React
useEffect(() => {
  window.api.getVersion().then(console.log);
}, []);

번들링 전략: Webpack 분리 구성

처음에는 "그냥 CRA(Create React App) 쓰고 main.js 하나 붙이면 되는 거 아닌가?"라고 생각할 수 있습니다. 하지만 개발이 진척되면서 다음과 같은 문제가 발생합니다:

  • React의 빌드 결과물을 Electron이 못 찾음
  • Node.js API를 잘못 참조해서 앱이 크래시 남
  • 하나의 Webpack 설정으로는 서로 다른 target을 만족시키기 어려움

이 때문에 main, renderer, preload 각각에 맞는 Webpack 설정이 필요합니다.

📦 webpack/
├─ webpack.main.js
├─ webpack.renderer.js
├─ webpack.preload.js

 

이 분리는 Electron에서 흔히 말하는 Three-Point Build 구조로 이어집니다. 실무에서 확장성 높은 프로젝트를 구축할 때 반드시 고려해야 할 부분입니다.


마치며

React와 Electron의 결합을 할 때는 둘은 전혀 다른 환경이기 때문에 의도적으로 분리하고 연결해야 한다는 점을 명심해야 합니다. IPC 구조나 preload를 통해 보안성을 높이는 것은 추후 테스트 커버리지 측면에서도 안정성을 높이는 데 도움을 줍니다.