Skip to content

Split View: Tauri 2 — 정말로 Electron을 대체하는가, 아니면 다른 도구인가

|

Tauri 2 — 정말로 Electron을 대체하는가, 아니면 다른 도구인가

프롤로그 — 왜 다시 데스크톱 프레임워크 이야기인가

"Electron은 무겁다"는 말은 너무 자주 들어서 이제 클리셰가 됐다. Slack을 띄우면 RAM 400MB가 사라지고, VS Code는 1GB까지 올라가고, 디스코드 인스톨러는 90MB다. 매번 같은 Chromium 사본이 사용자의 디스크와 메모리를 점령한다.

대안은 늘 거론됐다. 네이티브로 짜라(인력 두 배), Flutter 데스크톱(아직 어색하다), Qt(상업 라이선스), 그리고 — Tauri.

2024년 10월 Tauri 2.0이 GA됐다. 2026년 현재, 출시 후 1년 반이 지났다. 약속은 강력했다: 10MB 번들, 시스템 웹뷰, Rust 코어, iOS/Android까지. 약속만 보면 Electron의 자리를 차지할 것 같다.

그런데 실제로는? Slack은 여전히 Electron이다. VS Code도 Electron이다. Discord도 Electron이다. 큰 앱들이 마이그레이션하지 않은 이유가 있다. 동시에 SilentKeys, Voxly, Watson.ai, Pake 같은 새 앱들은 처음부터 Tauri로 짰다. 새 프로젝트와 기존 프로젝트의 선택이 갈린다.

이 글은 그 갈림을 정리한다. 아키텍처의 진짜 차이, 측정 가능한 트레이드오프(번들 크기, 메모리, 시작 시간), 시스템 웹뷰가 가져오는 그늘, 보안 모델의 작동 방식, 모바일 지원의 현실, 그리고 "우리 팀에 맞는가"를 판단하는 기준. Electron과 Tauri 둘 중 하나가 모두에게 정답이라는 식의 글은 아니다.

핵심 한 줄: Tauri는 Electron을 "더 가볍게 만든 것"이 아니라, 다른 트레이드오프를 고른 다른 도구다. 그 트레이드오프를 알면 선택이 쉬워진다.


1장 · 아키텍처 — 무엇이 어떻게 다른가

Tauri의 차별점은 한 줄로 요약할 수 있다: Chromium을 번들하지 않는다. 대신 OS의 시스템 웹뷰를 쓴다.

Electron 앱의 구성
┌────────────────────────────────────────────────┐
│  당신의 앱                                       │
│  ├─ 메인 프로세스: Node.js 런타임                │
│  ├─ 렌더러 프로세스: Chromium (V8 + Blink)       │
│  └─ Electron 바이너리 (~80~120MB)               │
│                                                │
│  → 같은 OS에 같은 Chromium이 N개 사본 존재       │
└────────────────────────────────────────────────┘

Tauri 앱의 구성
┌────────────────────────────────────────────────┐
│  당신의 앱                                       │
│  ├─ Rust 코어 (~3~5MB)                          │
│  ├─ 시스템 웹뷰 호출:                            │
│  │    macOS  → WKWebView (Safari/WebKit)        │
│  │    Windows → WebView2 (Edge/Chromium)        │
│  │    Linux  → WebKitGTK                        │
│  └─ 웹 자산(HTML/JS/CSS)                         │
│                                                │
│  → OS가 이미 갖고 있는 웹뷰를 빌려 쓴다           │
└────────────────────────────────────────────────┘

차이의 영향:

  • 번들 크기: Electron 80200MB → Tauri 240MB (대부분 5~15MB)
  • 메모리: Electron 200400MB 유휴 → Tauri 3080MB 유휴
  • 시작 시간: Electron 13초 → Tauri 0.20.8초
  • 렌더링 일관성: Electron은 동일, Tauri는 OS마다 다른 엔진 (이게 핵심 트레이드오프)

Tauri의 백엔드는 Rust다. 백엔드 코드(파일 시스템, 네트워크, 시스템 API 호출)는 Rust로 짜고, 프런트엔드(React/Vue/Svelte/Solid 등)는 평소대로 웹 기술로 짠다. 둘 사이는 IPC로 메시지를 주고받는다. Electron의 main/renderer 구조와 개념적으로 같지만, "main"이 Node.js가 아니라 Rust라는 게 다르다.

한 줄 요약: Electron은 "브라우저 + Node를 통째로 들고 다닌다", Tauri는 "OS가 가진 웹뷰를 빌리고 백엔드만 Rust로 컴파일한다".


2장 · 번들 크기와 메모리 — 측정 가능한 차이

추상적 비교는 그만하고 숫자로 보자. 2026년 기준 공개 벤치마크와 마이그레이션 사례에서 모은 수치다.

번들 크기 (.dmg / .msi / .AppImage)

앱 유형ElectronTauri비율
최소 "Hello World"85MB2.5MB34배
중간 규모(에디터, 채팅)120~160MB8~15MB약 12배
무거운 앱(IDE급)180~250MB25~40MB약 7배
인스톨러 평균80~150MB3~10MB약 20배

메모리 (유휴 상태)

ElectronTauri
빈 윈도우 1개180~250MB25~45MB
작은 UI 화면220~300MB35~60MB
중간 복잡도 SPA300~500MB60~120MB

시작 시간 (cold start, M2 MacBook Pro)

ElectronTauri
최소 앱0.8~1.5초0.15~0.35초
중간 규모1.5~3초0.4~0.9초

이 숫자가 의미하는 것: 번들·메모리·시작 시간은 진짜로 차이가 난다. 한 마이그레이션 사례는 "인스톨러 95% 축소, 메모리 70% 감소"를 보고했다. 측정 가능한 이득이다.

하지만 — 이 숫자가 모든 걸 결정하지는 않는다. 다음 장의 함정을 보자.


3장 · 시스템 웹뷰의 그늘 — "한 번 짜고 어디서나 돈다"의 거짓말

Tauri의 가장 큰 약점은 가장 큰 장점에서 나온다. 시스템 웹뷰라서 가볍지만, 시스템 웹뷰라서 OS마다 렌더링이 다르다.

같은 React 코드를 실행해도:
  macOS    → WKWebView (Safari) — Safari의 버그/지원 한계가 그대로 옴
  Windows  → WebView2 (Edge/Chromium) — Chromium이라 가장 호환적
  Linux    → WebKitGTK — 가장 뒤처짐. 일부 최신 CSS/JS API 미지원

이게 무슨 뜻인가:

  • backdrop-filter: macOS/Windows에서는 잘 되지만 WebKitGTK는 부분만 지원
  • :has() 셀렉터: 2026년에도 WebKitGTK 버전에 따라 갈림
  • OffscreenCanvas: macOS/Windows OK, Linux는 케이스별
  • WebRTC, MediaRecorder: 플랫폼마다 코덱·동작이 다르다
  • 폰트 렌더링: macOS의 antialiasing이 Windows·Linux와 다르게 보임
  • 드래그 앤 드롭, 클립보드 API: 각 웹뷰의 버그가 다 보인다

Electron은 이 문제를 정의상 갖지 않는다. 어디서 돌리든 같은 Chromium이라 같은 픽셀이 나온다. 비싸지만 일관성을 산다.

Tauri로 짤 때 실제 코드는 이렇게 된다:

/* Linux WebKitGTK 폴백을 가정해야 한다 */
.frosted {
  background: rgba(255, 255, 255, 0.85);
}

@supports (backdrop-filter: blur(10px)) {
  .frosted {
    background: rgba(255, 255, 255, 0.5);
    backdrop-filter: blur(10px);
  }
}

Linux 사용자가 적은 앱이라면 무시해도 좋다. 하지만 개발자 도구를 만든다면 Linux 사용자 비율이 30~40%일 수 있다. 그러면 모든 새 CSS/JS 기능에 폴백을 짜야 한다. "Chromium에서 되니 끝"이 안 된다.

한 줄 요약: Tauri는 "한 번 짜고 어디서나 돈다"가 아니라 "한 번 짜고 OS 3개에서 테스트한다"다. Electron보다 QA 비용이 더 든다.


4장 · 보안 모델 — allowlist에서 capabilities로

Tauri 2의 큰 변화 중 하나가 보안 모델이다. v1의 allowlist는 단순한 켜기/끄기 토글이었다 — "fs API를 켤지 끌지". 이게 한계에 부딪혀서 v2는 permissions / scopes / capabilities 3계층으로 재설계됐다.

개념 3개

  • Permissions: Tauri 명령에 대한 켜기/끄기 (예: fs:allow-read-file)
  • Scopes: 명령의 파라미터 검증 (예: "이 경로 패턴만 읽기 허용")
  • Capabilities: 권한과 스코프를 윈도우/웹뷰에 묶어 부여

이걸 JSON/TOML로 선언한다. src-tauri/capabilities/ 디렉터리에 파일을 두면 자동으로 활성화된다.

{
  "$schema": "../gen/schemas/desktop-schema.json",
  "identifier": "main-capability",
  "description": "메인 윈도우에 부여하는 권한 묶음",
  "windows": ["main"],
  "permissions": [
    "core:default",
    "fs:allow-read-text-file",
    "fs:allow-write-text-file",
    {
      "identifier": "fs:scope",
      "allow": [
        { "path": "$APPDATA/notes/*" },
        { "path": "$DOCUMENT/MyApp/**" }
      ],
      "deny": [
        { "path": "$HOME/.ssh/*" }
      ]
    },
    "shell:allow-open"
  ]
}

이 설정이 의미하는 것:

  • 메인 윈도우만 이 권한들을 받는다 (다른 윈도우는 별도 capability 필요)
  • 파일 시스템 읽기/쓰기는 켜져 있지만 — $APPDATA/notes/$DOCUMENT/MyApp/ 하위만
  • $HOME/.ssh/* 는 명시적 거부
  • shell:open은 허용 (이메일/URL 열기용)

Electron에는 이런 선언적 모델이 없다. Electron에서는 보안이 코드 패턴이다 — contextIsolation: true, nodeIntegration: false, preload 스크립트, IPC 화이트리스트를 손으로 짜야 한다. 잊으면 RCE 취약점이 된다.

Tauri의 모델이 더 안전한 이유:

  1. 선언적: 권한이 코드가 아니라 설정이라 감사하기 쉽다
  2. 세분화: 파일 시스템 전체가 아니라 특정 경로만
  3. 기본 거부: capability를 명시하지 않으면 IPC 명령이 차단된다
  4. 윈도우별: 메인 윈도우와 about 윈도우의 권한을 분리

물론 Electron도 신경 써서 짜면 같은 수준이 된다. 차이는 — Tauri는 그게 기본값이고, Electron은 그게 추가 작업이다.

한 줄 요약: capabilities는 "기본 안전한 IPC"를 강제한다. Electron에서 신경 안 쓰면 RCE가 되는 패턴이, Tauri에서는 capability 파일을 안 적으면 IPC가 그냥 거부된다.


5장 · IPC 계약 — 명령 하나를 끝까지 보기

추상적 이야기 그만하고, 실제 IPC 명령 하나를 처음부터 끝까지 보자. 시나리오: 프런트엔드에서 "노트 파일 저장" 버튼을 누르면 백엔드(Rust)가 디스크에 쓴다.

Rust 측: 명령 정의

src-tauri/src/commands.rs:

use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use tauri::AppHandle;
use tauri_plugin_fs::FsExt;

#[derive(Debug, Serialize, Deserialize)]
pub struct Note {
    pub id: String,
    pub title: String,
    pub body: String,
}

#[derive(Debug, Serialize)]
pub struct SaveResult {
    pub bytes_written: usize,
    pub path: String,
}

#[tauri::command]
pub async fn save_note(
    app: AppHandle,
    note: Note,
) -> Result<SaveResult, String> {
    // 1) 입력 검증 — IPC 경계는 신뢰할 수 없다
    if note.id.is_empty() || note.id.contains('/') || note.id.contains('\\') {
        return Err("invalid note id".into());
    }
    if note.body.len() > 10 * 1024 * 1024 {
        return Err("note too large (>10MB)".into());
    }

    // 2) capability가 허용하는 경로 안에서만 작업
    let app_data = app
        .path()
        .app_data_dir()
        .map_err(|e| format!("no app data dir: {e}"))?;
    let mut path: PathBuf = app_data.join("notes");
    std::fs::create_dir_all(&path).map_err(|e| e.to_string())?;
    path.push(format!("{}.json", note.id));

    // 3) 직렬화하고 쓴다
    let body = serde_json::to_vec_pretty(&note).map_err(|e| e.to_string())?;
    let bytes = body.len();
    std::fs::write(&path, body).map_err(|e| e.to_string())?;

    Ok(SaveResult {
        bytes_written: bytes,
        path: path.display().to_string(),
    })
}

src-tauri/src/lib.rs:

pub fn run() {
    tauri::Builder::default()
        .plugin(tauri_plugin_fs::init())
        .invoke_handler(tauri::generate_handler![
            commands::save_note,
        ])
        .run(tauri::generate_context!())
        .expect("tauri 실행 실패");
}

TypeScript 측: 호출

src/lib/notes.ts:

import { invoke } from '@tauri-apps/api/core'

export interface Note {
  id: string
  title: string
  body: string
}

export interface SaveResult {
  bytesWritten: number
  path: string
}

export async function saveNote(note: Note): Promise<SaveResult> {
  // invoke는 Rust 측 #[tauri::command] 이름과 1:1로 매핑된다
  // 직렬화는 자동 (serde ↔ JSON)
  // 단, 필드 이름은 snake_case ↔ camelCase 매핑 규칙 확인 필요
  const raw = await invoke<{ bytes_written: number; path: string }>('save_note', {
    note,
  })
  return { bytesWritten: raw.bytes_written, path: raw.path }
}

React 컴포넌트에서:

import { useState } from 'react'
import { saveNote } from './lib/notes'

export function NoteEditor() {
  const [title, setTitle] = useState('')
  const [body, setBody] = useState('')
  const [status, setStatus] = useState<string | null>(null)

  async function onSave() {
    try {
      const result = await saveNote({
        id: crypto.randomUUID(),
        title,
        body,
      })
      setStatus(`저장됨: ${result.path} (${result.bytesWritten} bytes)`)
    } catch (e) {
      setStatus(`실패: ${String(e)}`)
    }
  }

  return (
    <div>
      <input value={title} onChange={(e) => setTitle(e.target.value)} />
      <textarea value={body} onChange={(e) => setBody(e.target.value)} />
      <button onClick={onSave}>저장</button>
      {status && <p>{status}</p>}
    </div>
  )
}

이게 어떻게 안전한가

  1. capability에서 명시적 허용src-tauri/capabilities/main.jsonsave_note가 있는 윈도우만 호출 가능
  2. Rust 입력 검증 — id에 슬래시가 있으면 디렉터리 탈출 시도, 크기 제한도 명시
  3. 경로 한정app_data_dir()로만 작업, 사용자가 임의 경로를 주입할 수 없음
  4. 타입 안전 — Rust 측은 컴파일러가, TS 측은 invoke의 제네릭이 검증

Electron으로 같은 걸 짜면 preload + contextBridge + IPC 핸들러 + 명시적 화이트리스트를 직접 구성해야 한다. 분량은 비슷한데, 잊으면 보안 구멍이 된다.

한 줄 요약: Tauri의 IPC는 Rust의 타입 시스템과 capability 시스템이 함께 강제한다. 코드 옆에 보안이 붙어 있다.


6장 · 모바일 — Tauri 2의 진짜 큰 변화

Tauri 1은 데스크톱만 지원했다. v2가 발표하면서 가장 크게 광고된 게 iOS · Android 지원이다. 2026년 현재, 이게 진짜로 어디까지 왔는지가 중요하다.

무엇이 되는가

  • iOS와 Android 빌드가 안정 (Tauri 2.0 GA부터)
  • 같은 웹 프런트엔드 코드를 데스크톱과 공유
  • Rust 백엔드를 모바일에서도 호출 (네이티브 플러그인은 Swift/Kotlin/Java로 작성 가능)
  • 기본 플러그인(파일 시스템, 노티피케이션, 다이얼로그 등) 중 상당수가 모바일에서 작동

무엇이 아직 거친가

  • 데스크톱 플러그인 = 모바일 플러그인 등호 아님. 일부 플러그인은 데스크톱만, 일부는 모바일만 지원
  • Tauri 팀 스스로 인정 — "모바일 일등 시민" 약속은 과도했고, 커뮤니티와 함께 만들어가는 중이라고 GA 회고에 적혀 있다
  • iOS 사이드로딩의 어려움 (Apple 정책), Android는 상대적으로 자유
  • Flutter나 React Native만큼 모바일에 최적화된 UX를 자동으로 주지는 않는다 — 어차피 웹뷰 기반이라 모바일 네이티브 느낌을 위한 추가 작업 필요

언제 Tauri 모바일을 선택하나

시나리오추천
이미 Tauri 데스크톱 앱이 있고, 같은 코드를 모바일로 확장Tauri 모바일 좋음
모바일 우선 앱, 데스크톱은 부차적React Native / Flutter / 네이티브
데스크톱과 모바일 모두 일급, 코드 공유 중요진지하게 Tauri 검토
모바일 네이티브 UX가 핵심(제스처, 애니메이션)네이티브 또는 Flutter

한 줄 요약: Tauri 모바일은 "데스크톱 앱의 모바일 동반자"로는 훌륭하고, "모바일 우선 앱의 기반"으로는 아직 React Native/Flutter만큼 무르익지 않았다.


7장 · Electron · Wails · Neutralino · 네이티브 — 비교 매트릭스

각 도구의 자리를 정리한다.

항목Tauri 2ElectronWailsNeutralino네이티브
백엔드 언어RustNode.jsGoC/C++(코어)Swift/Kotlin/C++/C#
렌더링시스템 웹뷰Chromium 번들시스템 웹뷰시스템 웹뷰네이티브 UI
번들 크기5~40MB80~200MB10~25MB2~10MB1~20MB
메모리 유휴30~80MB200~400MB50~100MB30~70MB20~80MB
모바일iOS/Android없음없음없음우선 시민
렌더링 일관성낮음(OS별 다름)높음낮음낮음높음
학습 곡선높음(Rust)낮음(JS)중간(Go)낮음가장 높음
보안 모델capabilities(선언적)코드 패턴메소드 바인딩allowListOS API
성숙도높음(2024 GA)매우 높음중간중간최고
생태계빠르게 성장거대작음작음OS 의존
기업 후원재단(독립)OpenJS커뮤니티커뮤니티플랫폼 벤더

각 도구의 한 줄 요약

  • Tauri 2: 가볍고 안전한 데스크톱·모바일 앱. Rust를 배울 의지가 있다면 베스트.
  • Electron: 가장 폭넓은 생태계, 일관된 렌더링. 무거움을 받아들이는 대가로 빠른 개발.
  • Wails: Go 팀의 자연스러운 선택. Go의 단순함을 그대로 데스크톱에 가져옴. 다만 일부 기능(멀티 윈도우 등)이 아직 진행 중.
  • Neutralino: 초경량. 컴파일 없이 Node 비슷한 API. 가장 가볍지만 가장 미성숙.
  • 네이티브: 플랫폼당 한 팀씩. 최고의 UX, 가장 비싸다.

8장 · Rust 학습 곡선 — 프런트엔드 팀의 진짜 비용

Tauri의 가장 큰 진입 장벽은 번들 크기도, 시스템 웹뷰도 아니다. Rust다. 프런트엔드 중심 팀에 Rust를 도입하는 비용을 정직하게 따져보자.

좋은 소식

  • Tauri는 "프런트엔드는 평소대로 + 백엔드만 Rust" 구조다. 백엔드 코드가 적은 단순 앱이라면 Rust 코드는 IPC 명령 몇 개와 main.rs 정도로 끝난다
  • Tauri CLI가 보일러플레이트의 90%를 만들어준다 — Rust 빌드 시스템(cargo), 크로스 컴파일, 코드 사이닝 설정을 대부분 추상화
  • ChatGPT/Claude 같은 AI 도구가 Rust 코드 생성을 잘한다. 단순 IPC 명령은 AI가 대부분 짜준다
  • 백엔드가 단순하면 Rust의 어려운 부분(라이프타임, 비동기 깊이, unsafe)을 거의 만나지 않음

나쁜 소식

  • 백엔드가 복잡해지는 순간 — DB 연결 풀, 비동기 작업, 동시성 처리 — Rust의 학습 곡선이 가팔라진다
  • 컴파일 시간이 길다. 초기 빌드 515분, 증분 빌드 530초. Electron은 즉시 리로드
  • 에러 메시지가 친절하지만 양이 많다. JS 한 줄 에러가 Rust에서는 30줄짜리 트레잇 바운드 설명이 될 수 있음
  • 팀 내 Rust 코드를 리뷰할 사람이 1명뿐이면 그 사람이 병목이 된다
  • 의존성(crate) 보안 감사. JS의 npm audit보다 도구가 적고, 일부 crate는 unsafe를 많이 씀

현실적인 결정 기준

팀 상황추천
프런트엔드 전담, Rust 경험 없음, 백엔드 단순Tauri 시도해볼 만함
프런트엔드 전담, 백엔드 복잡(DB·동시성)Electron + Node 백엔드 또는 사이드카 패턴
Rust 경험 있는 팀원 1명 이상Tauri 강력 추천
시스템 프로그래밍 배경 있는 팀Tauri 자연스러움
6개월 안에 출시해야 함, 일정 빡빡Electron — 모든 게 검증돼 있음

흥미로운 패턴: 많은 팀이 사이드카 패턴으로 절충한다. Tauri 메인은 가볍게 두고, 무거운 백엔드 로직(예: Python 머신러닝, Node 서비스)은 별도 프로세스로 띄워 Tauri가 관리. 이러면 Rust 학습 비용을 최소화하면서 Tauri의 번들·메모리 이득은 챙긴다.

한 줄 요약: 프런트엔드 팀에게 Rust는 진짜 비용이다. 백엔드 복잡도가 낮으면 감당할 만하고, 높으면 다른 도구가 낫다.


9장 · 실제로 Tauri 2로 만들어진 앱들

2026년 현재 Tauri 2로 출시된 주목할 만한 앱들. 이름과 카테고리, 그리고 왜 Tauri를 골랐는지 추정.

카테고리왜 Tauri인가(추정)
SilentKeys프라이버시 우선 음성 받아쓰기온디바이스 ML, 작은 번들, 시스템 통합
VoxlyAI 음성 받아쓰기빠른 시작, 작은 메모리, 시스템 트레이 통합
Watson.ai회의 녹음 · 추출백그라운드 동작, 시스템 권한 정교한 제어
Pake웹페이지를 데스크톱 앱으로작은 번들이 핵심 가치(Electron보다 명확한 이득)
RivetAI 에이전트 비주얼 프로그래밍무거운 IDE 같은 UX, 메모리 절약 중요
XGetter동영상 · 오디오 다운로더시스템 통합, 작은 인스톨러
Kunobi / KubeliKubernetes 데스크톱 클라이언트개발자 도구, 빠른 시작, MCP 서버 내장

공통 패턴이 보인다:

  1. 새 프로젝트 — 기존 Electron 앱을 옮긴 게 아니라 처음부터 Tauri 선택
  2. 작은~중간 규모 — 거대 IDE가 아니라 단일 목적이 명확한 앱
  3. 시스템 통합 중요 — 트레이, 백그라운드, 글로벌 단축키, 시스템 권한
  4. 메모리 민감 — 백그라운드에서 계속 도는 앱(받아쓰기, 회의 녹음 등)
  5. 개발자 또는 파워 유저 타깃 — 사용자가 Linux 비율도 일정 부분

반대로 마이그레이션 사례가 적은 이유:

  • 기존 Electron 앱의 코드 자산이 크다 — Rust로 백엔드 다시 짜는 비용
  • 사용자가 이미 만족 — 깨지지 않은 걸 고치지 마라
  • Linux 사용자 비율이 낮으면 시스템 웹뷰 차이를 신경 안 써도 됨

한 줄 요약: Tauri는 "새 프로젝트에서 의식적으로 선택"되는 경우가 압도적이다. 기존 Electron 앱 마이그레이션은 큰 동기가 있을 때만 정당화된다.


10장 · 언제 무엇을 고르나 — 의사결정 트리

시작:
├─ 모바일이 우선인가?
│   ├─ 예 → React Native / Flutter / 네이티브
│   └─ 아니오 → 다음
├─ Linux를 진지하게 지원해야 하나?
│   ├─ 예, Linux 사용자가 30%+ → 시스템 웹뷰 차이를 감수할 수 있나?
│   │       ├─ 예 → Tauri / Wails
│   │       └─ 아니오, 픽셀 일관성 필요 → Electron
│   └─ 아니오 또는 적음 → 다음
├─ 팀에 Rust 경험이 있나?
│   ├─ 예 또는 학습 의지 있음 → Tauri 강력 추천
│   ├─ Go 경험 있음 → Wails 검토
│   └─ JS만 가능, 학습 시간 없음 → Electron
├─ 번들 크기 · 메모리가 진짜로 중요한가?
│   ├─ 예(백그라운드 앱, 저사양 기기 타깃) → Tauri
│   └─ 아니오, 사용자가 신경 안 씀 → Electron이 빠름
├─ 백엔드 로직이 복잡한가?
│   ├─ 예, DB · 동시성 많음 → Electron 또는 Tauri + 사이드카
│   └─ 아니오, 주로 UI → Tauri 또는 Neutralino
└─ 일정이 빡빡한가? (3~6개월)
    ├─ 예 → Electron (모든 게 검증됨)
    └─ 아니오, 1년+ → Tauri 학습 비용 회수 가능

단순 규칙

  • 새 프로젝트, 단순한 백엔드, 번들 크기 중요 → Tauri
  • 기존 Electron 앱, 사용자 만족 → 그대로 유지
  • AAA급 데스크톱 UX, Linux 일급 시민 → Electron
  • 데스크톱 + 모바일 코드 공유 중요 → Tauri 또는 Flutter
  • Go 팀 → Wails
  • 번들 정말 1MB라도 줄이고 싶음 → Neutralino (대신 미성숙)
  • OS별 최고의 UX, 인력 무한 → 네이티브

11장 · 안티패턴과 함정

Tauri를 선택해놓고 후회하는 패턴들.

"Electron처럼 짜면 되겠지"

  • 증상: Rust 백엔드를 거대한 단일 파일로 만들고 모든 IPC 명령을 거기 박는다
  • 결과: 컴파일 시간 폭발, 변경 비용 증가, 테스트 불가
  • 대신: Rust 모듈로 도메인 분리(commands/, services/, models/), 명령은 얇게(검증 + 서비스 호출)

"capability를 다 켜기"

  • 증상: core:default에 더해 모든 fs/shell/dialog 권한을 와이드카드로 켜기
  • 결과: 보안 모델의 핵심 가치를 버림. Electron의 위험을 그대로 얻음
  • 대신: 진짜 필요한 것만, 스코프를 좁게. 윈도우별 capability 분리

"Rust 비동기를 처음부터 깊게"

  • 증상: 단순 IPC를 tokio::spawn과 채널 6개로 구현
  • 결과: 학습 곡선 폭발, 데드락, 컴파일 에러의 늪
  • 대신: 처음에는 async fn + Tauri의 기본 런타임. 비동기가 진짜 필요할 때만 깊이 들어감

"시스템 웹뷰 차이를 무시"

  • 증상: macOS에서만 개발, 출시 직전 Linux 테스트
  • 결과: WebKitGTK에서 절반의 UI가 깨짐
  • 대신: 첫 주부터 OS 3개 CI에서 빌드, 핵심 화면은 모든 OS에서 스크린샷 비교

"프런트엔드도 Rust로 짜겠다"

  • 증상: Leptos/Yew/Dioxus로 프런트까지 Rust
  • 결과: Rust 학습 곡선 2배, 생태계는 React/Vue의 100분의 1
  • 대신: 프런트는 평소 도구(React/Vue/Svelte), 백엔드만 Rust. Tauri의 설계가 그렇게 돼 있다

"AI 에이전트에게 Tauri 앱을 통째로 짜라고 요청"

  • 증상: 에이전트가 capability 설정과 Rust IPC 패턴을 어설프게 만들어 보안 구멍
  • 결과: 권한이 너무 넓거나, 입력 검증이 빠지거나, 사이드 채널이 열림
  • 대신: 에이전트에게 capability와 IPC 명령은 사람이 리뷰, 단위 테스트는 IPC 경계 케이스를 명시적으로 강제

12장 · 마이그레이션 — Electron에서 Tauri로 옮긴다면

옮기는 게 정당화되는 시나리오가 있다면, 단계는 다음과 같다.

1. 측정: 현재 Electron 앱의 번들, 메모리, 시작 시간을 기록
2. 백엔드 인벤토리: Node 의존성을 모두 나열 — 어떤 게 Rust crate로 대체되나?
3. 프런트 분리: UI는 그대로 가져갈 수 있는지(대부분 가능)
4. IPC 매핑: 현재 ipcMain 핸들러 하나하나를 Tauri 명령으로 매핑
5. capability 설계: 각 IPC가 무엇에 접근하는지 정리 → capabilities 파일
6. 시스템 통합: 트레이/노티/단축키를 Tauri 플러그인으로 재구현
7. 크로스 OS 테스트: 첫날부터 3개 OS에서 빌드, 시각 회귀 테스트
8. 측정 재확인: 약속된 수치(메모리/번들)가 실제로 나오는지 검증
9. 베타 사용자 → 점진적 출시

옮기지 말아야 할 신호

  • Node 의존성에 Rust 대체가 없는 게 많음(특정 머신러닝 라이브러리 등)
  • 사용자 70%가 macOS, 시스템 웹뷰 차이가 어차피 문제 안 됨, 메모리도 만족
  • 팀에 Rust 가능한 사람이 0명, 학습할 6개월이 없음
  • 앱이 사용자에게 충분히 빠르고, 번들 크기 불만이 없음

옮기는 게 정당화되는 신호

  • 백그라운드에서 24/7 도는 앱이라 메모리가 사용자 컴퓨터에 실제 부담
  • 인스톨러 크기가 다운로드 전환율에 영향(저사양 / 저대역 시장)
  • 보안 감사를 통과해야 하고 Electron의 보안 패턴을 다 정리하는 게 더 비쌈
  • 모바일 확장 계획이 있고, 데스크톱 코드를 재사용하고 싶음

에필로그 — 체크리스트, 안티패턴, 다음 글 예고

Tauri 2는 "Electron보다 가벼운 무언가"가 아니라 다른 트레이드오프를 고른 다른 도구다. 시스템 웹뷰의 그늘, Rust 학습 곡선, 작은 생태계를 받아들이고 — 번들·메모리·보안 모델의 이득을 챙긴다. 새 프로젝트, 단순한 백엔드, 번들이 중요한 상황이라면 합리적 선택이다. 기존 Electron 앱은 강한 동기가 없으면 옮기지 않아도 된다.

Tauri 선택 체크리스트

  1. 새 프로젝트인가, 기존 마이그레이션인가? — 새 프로젝트라면 진입 비용이 정당화됨
  2. 백엔드 복잡도는? — 단순할수록 Tauri가 자연스러움
  3. 모바일 계획이 있는가? — 있다면 Tauri 2의 모바일 지원이 매력적
  4. 팀의 Rust 경험은? — 0명이면 학습 일정을 일정에 더해야 함
  5. Linux 사용자 비율은? — 높으면 시스템 웹뷰 QA 비용을 계획에 포함
  6. 번들·메모리가 진짜 중요한가? — 백그라운드 앱, 저사양 기기 타깃이면 이득
  7. 일정 여유는? — 빡빡하면 Electron, 1년+면 Tauri 학습 회수 가능
  8. capability 설계를 일찍 시작했는가? — 보안 모델이 코드 구조를 좌우
  9. 사이드카 패턴이 필요한가? — 무거운 백엔드는 별도 프로세스로 분리
  10. OS 3개 CI를 첫 주에 구성했는가? — 마지막 주에 발견하면 늦음

안티패턴

안티패턴왜 나쁜가대신
번들 크기만 보고 Tauri 선택Rust 학습 비용과 QA 비용 무시팀과 일정도 같이 평가
Electron 코드를 그대로 Rust로 1:1 포팅Rust 관용구가 달라 어색도메인부터 다시 설계
capability를 와이드카드로 켜기보안 모델의 가치 소멸진짜 필요한 권한만, 스코프 좁게
Linux 테스트를 마지막에출시 직전 발견첫 주부터 3개 OS CI
프런트도 Rust로 짜기(Leptos 등)생태계 100분의 1프런트는 React/Vue, 백엔드만 Rust
거대한 commands.rs 하나컴파일 시간 폭발도메인별 모듈로 분리
Rust 비동기를 깊게 처음부터학습 곡선 절벽동기로 시작, 필요할 때만 비동기
시스템 웹뷰의 일관성 가정macOS만 보고 출시CSS·JS 기능은 폴백 + 모든 OS 시각 회귀
모바일을 React Native 대체로 기대모바일 우선 UX 아직 부족데스크톱 동반자로 사용
마이그레이션을 ROI 없이 강행코드 자산 낭비, 사용자 영향측정 가능한 이득이 있을 때만

다음 글 예고

다음 글은 **"Rust 비동기의 함정 — async/await, tokio, send 트레이트, 그리고 라이프타임이 만나는 곳"**이다. 이번 글이 "Tauri를 선택할까"의 글이라면, 다음 글은 "Tauri 백엔드가 복잡해지기 시작할 때 무엇을 만나는가"의 글이다. Rust 비동기는 강력하지만 함정이 많다 — Send/Sync 트레이트, 라이프타임과 비동기의 상호작용, 채널 선택(mpsc / oneshot / broadcast), 셀렉트 패턴, 그리고 비동기 컨텍스트에서 동기 코드를 실행하는 법까지. Tauri 백엔드든, 백엔드 마이크로서비스든, Rust 비동기를 진지하게 쓰는 모든 곳에 적용된다.


참고 / References

Tauri 2 — Does It Actually Replace Electron, or Is It a Different Tool?

Prologue — Why we are talking about desktop frameworks again

"Electron is heavy" has been said so often it became a cliché. Boot Slack and 400MB of RAM is gone. VS Code climbs to 1GB. The Discord installer is 90MB. Every time, the same copy of Chromium occupies the user's disk and memory.

Alternatives have been proposed forever. Write native (double the headcount), Flutter desktop (still awkward), Qt (commercial license), and — Tauri.

Tauri 2.0 went GA in October 2024. In 2026, we are eighteen months past that release. The promises were strong: 10MB bundles, system webview, Rust core, iOS and Android support. On paper, it looked like Electron's replacement.

In practice? Slack is still Electron. VS Code is still Electron. Discord is still Electron. The big apps have reasons not to migrate. At the same time, fresh apps like SilentKeys, Voxly, Watson.ai, and Pake chose Tauri from day one. New projects and existing projects diverge.

This post walks the divide. The real architectural differences, measurable trade-offs (bundle size, memory, startup time), the shadow the system webview casts, how the security model works, the actual state of mobile support, and how to decide whether Tauri fits your team. Neither Electron nor Tauri is the right answer for everyone.

One-line take: Tauri is not "Electron made lighter." It is a different tool with different trade-offs. Know the trade-offs and the decision becomes easy.


1. Architecture — what is actually different

The core difference fits in one sentence: Tauri does not bundle Chromium. It uses the OS's system webview instead.

Anatomy of an Electron app
┌────────────────────────────────────────────────┐
│  Your app                                       │
│  ├─ Main process: Node.js runtime               │
│  ├─ Renderer process: Chromium (V8 + Blink)     │
│  └─ Electron binary (~80-120MB)                 │
│                                                │
│  → N copies of the same Chromium on the OS      │
└────────────────────────────────────────────────┘

Anatomy of a Tauri app
┌────────────────────────────────────────────────┐
│  Your app                                       │
│  ├─ Rust core (~3-5MB)                          │
│  ├─ System webview call:                        │
│  │    macOS  → WKWebView (Safari/WebKit)        │
│  │    Windows → WebView2 (Edge/Chromium)        │
│  │    Linux  → WebKitGTK                        │
│  └─ Web assets (HTML/JS/CSS)                    │
│                                                │
│  → Borrows the webview the OS already ships     │
└────────────────────────────────────────────────┘

Consequences of the difference:

  • Bundle size: Electron 80-200MB, Tauri 2-40MB (typically 5-15MB)
  • Memory: Electron 200-400MB at idle, Tauri 30-80MB at idle
  • Startup time: Electron 1-3 seconds, Tauri 0.2-0.8 seconds
  • Rendering consistency: Electron is identical everywhere; Tauri runs a different engine on each OS (this is the central trade-off)

Tauri's backend language is Rust. Backend code — filesystem, network, system APIs — is written in Rust. The frontend (React, Vue, Svelte, Solid, whatever) stays the same. The two talk over IPC. Conceptually identical to Electron's main/renderer split, except "main" is Rust instead of Node.

Summary: Electron ships a whole browser plus Node. Tauri borrows the OS's webview and compiles only the backend to Rust.


2. Bundle size and memory — the measurable difference

Enough abstraction. Numbers, from 2026 public benchmarks and migration reports.

Bundle size (.dmg / .msi / .AppImage)

App typeElectronTauriRatio
Minimal "Hello World"85MB2.5MB34x
Medium (editor, chat)120-160MB8-15MB~12x
Heavy (IDE-class)180-250MB25-40MB~7x
Installer average80-150MB3-10MB~20x

Memory (idle)

AppElectronTauri
Single empty window180-250MB25-45MB
Small UI surface220-300MB35-60MB
Medium-complexity SPA300-500MB60-120MB

Cold start time (M2 MacBook Pro)

AppElectronTauri
Minimal0.8-1.5s0.15-0.35s
Medium1.5-3s0.4-0.9s

What these numbers mean: bundle, memory, and startup really are different. One migration report cited 95% installer-size reduction and 70% lower memory. The wins are measurable.

But — the numbers don't decide everything. Read the next chapter.


3. The system-webview shadow — "write once, run anywhere" is a lie

Tauri's biggest weakness comes from its biggest strength. System webview makes it light; system webview also means rendering differs by OS.

The same React code, executed on:
  macOS    → WKWebView (Safari)    — inherits Safari bugs and gaps
  Windows  → WebView2 (Edge/Chromium) — most compatible since it is Chromium
  Linux    → WebKitGTK             — most behind; some modern CSS/JS APIs missing

What this means in practice:

  • backdrop-filter: fine on macOS/Windows, partial on WebKitGTK
  • :has() selector: in 2026 still varies by WebKitGTK version
  • OffscreenCanvas: macOS/Windows ok, Linux case-by-case
  • WebRTC, MediaRecorder: codecs and behavior differ per platform
  • Font rendering: macOS antialiasing looks different from Windows/Linux
  • Drag-and-drop, Clipboard API: each webview's quirks show through

Electron does not have this problem by definition. Everywhere you run it, the same Chromium produces the same pixels. Expensive, but you buy consistency.

Real Tauri code learns to look like this:

/* Assume a WebKitGTK fallback on Linux */
.frosted {
  background: rgba(255, 255, 255, 0.85);
}

@supports (backdrop-filter: blur(10px)) {
  .frosted {
    background: rgba(255, 255, 255, 0.5);
    backdrop-filter: blur(10px);
  }
}

If your Linux share is tiny, ignore it. But if you build developer tooling, Linux can be 30-40% of users. Then every new CSS/JS feature needs a fallback. "Works in Chromium, ship it" is no longer enough.

Summary: Tauri is not "write once, run anywhere." It is "write once, test on three OSes." The QA bill is higher than Electron's.


4. The security model — from allowlist to capabilities

A major change in Tauri 2 is the security model. The v1 allowlist was a flat on/off toggle: "enable the fs API or not." It hit its limits, so v2 was redesigned around three concepts: permissions, scopes, capabilities.

Three concepts

  • Permissions: on/off toggles for Tauri commands (e.g., fs:allow-read-file)
  • Scopes: parameter validation for commands (e.g., "only these path patterns")
  • Capabilities: bundles of permissions and scopes attached to specific windows/webviews

You declare them in JSON or TOML. Files dropped into src-tauri/capabilities/ are enabled automatically.

{
  "$schema": "../gen/schemas/desktop-schema.json",
  "identifier": "main-capability",
  "description": "Permissions granted to the main window",
  "windows": ["main"],
  "permissions": [
    "core:default",
    "fs:allow-read-text-file",
    "fs:allow-write-text-file",
    {
      "identifier": "fs:scope",
      "allow": [
        { "path": "$APPDATA/notes/*" },
        { "path": "$DOCUMENT/MyApp/**" }
      ],
      "deny": [
        { "path": "$HOME/.ssh/*" }
      ]
    },
    "shell:allow-open"
  ]
}

What this configuration means:

  • Only the main window receives these permissions (other windows need their own capability)
  • Filesystem read/write is enabled — but only under $APPDATA/notes/ and $DOCUMENT/MyApp/
  • $HOME/.ssh/* is explicitly denied
  • shell:open is allowed (for opening email and URLs)

Electron has no equivalent declarative model. Security in Electron is a code pattern — contextIsolation: true, nodeIntegration: false, preload scripts, a hand-maintained IPC whitelist. Forget any one of them and you ship an RCE.

Why the Tauri model is safer:

  1. Declarative: permissions are config, not code — easy to audit
  2. Granular: not "filesystem on or off" but specific paths
  3. Default deny: a command without a capability gets a hard "no"
  4. Per-window: a main window and an about window can have separate permissions

Electron can match this if you're careful. The difference is — in Tauri it is the default, in Electron it is extra work.

Summary: capabilities force "safe IPC by default." A pattern that becomes RCE in Electron if forgotten just gets rejected in Tauri if no capability is declared.


5. The IPC contract — one command, end to end

Enough abstraction. Walk a real IPC command end to end. Scenario: clicking "Save Note" in the frontend triggers a Rust backend that writes to disk.

Rust side: command definition

src-tauri/src/commands.rs:

use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use tauri::AppHandle;
use tauri_plugin_fs::FsExt;

#[derive(Debug, Serialize, Deserialize)]
pub struct Note {
    pub id: String,
    pub title: String,
    pub body: String,
}

#[derive(Debug, Serialize)]
pub struct SaveResult {
    pub bytes_written: usize,
    pub path: String,
}

#[tauri::command]
pub async fn save_note(
    app: AppHandle,
    note: Note,
) -> Result<SaveResult, String> {
    // 1) Validate input — the IPC boundary is untrusted
    if note.id.is_empty() || note.id.contains('/') || note.id.contains('\\') {
        return Err("invalid note id".into());
    }
    if note.body.len() > 10 * 1024 * 1024 {
        return Err("note too large (>10MB)".into());
    }

    // 2) Only work inside the paths a capability allows
    let app_data = app
        .path()
        .app_data_dir()
        .map_err(|e| format!("no app data dir: {e}"))?;
    let mut path: PathBuf = app_data.join("notes");
    std::fs::create_dir_all(&path).map_err(|e| e.to_string())?;
    path.push(format!("{}.json", note.id));

    // 3) Serialize and write
    let body = serde_json::to_vec_pretty(&note).map_err(|e| e.to_string())?;
    let bytes = body.len();
    std::fs::write(&path, body).map_err(|e| e.to_string())?;

    Ok(SaveResult {
        bytes_written: bytes,
        path: path.display().to_string(),
    })
}

src-tauri/src/lib.rs:

pub fn run() {
    tauri::Builder::default()
        .plugin(tauri_plugin_fs::init())
        .invoke_handler(tauri::generate_handler![
            commands::save_note,
        ])
        .run(tauri::generate_context!())
        .expect("tauri failed to start");
}

TypeScript side: the call

src/lib/notes.ts:

import { invoke } from '@tauri-apps/api/core'

export interface Note {
  id: string
  title: string
  body: string
}

export interface SaveResult {
  bytesWritten: number
  path: string
}

export async function saveNote(note: Note): Promise<SaveResult> {
  // invoke maps 1:1 to a Rust #[tauri::command] by name.
  // Serialization is automatic (serde <-> JSON).
  // Watch out for snake_case vs camelCase mapping rules.
  const raw = await invoke<{ bytes_written: number; path: string }>('save_note', {
    note,
  })
  return { bytesWritten: raw.bytes_written, path: raw.path }
}

In a React component:

import { useState } from 'react'
import { saveNote } from './lib/notes'

export function NoteEditor() {
  const [title, setTitle] = useState('')
  const [body, setBody] = useState('')
  const [status, setStatus] = useState<string | null>(null)

  async function onSave() {
    try {
      const result = await saveNote({
        id: crypto.randomUUID(),
        title,
        body,
      })
      setStatus(`Saved: ${result.path} (${result.bytesWritten} bytes)`)
    } catch (e) {
      setStatus(`Failed: ${String(e)}`)
    }
  }

  return (
    <div>
      <input value={title} onChange={(e) => setTitle(e.target.value)} />
      <textarea value={body} onChange={(e) => setBody(e.target.value)} />
      <button onClick={onSave}>Save</button>
      {status && <p>{status}</p>}
    </div>
  )
}

Why this is safe

  1. Explicit capability — only windows whose capability includes save_note can call it
  2. Rust input validation — slashes in the id are a directory-traversal attempt, size limits are explicit
  3. Path confinement — work happens under app_data_dir() only; no user-controlled path injection
  4. Type safety — the Rust compiler checks one side, invoke's generics check the other

Writing the same thing in Electron means assembling preload, contextBridge, IPC handlers, and explicit whitelists yourself. Roughly the same volume of code, but forgetting a piece becomes a security hole.

Summary: Tauri's IPC is enforced by Rust's type system and the capability system together. Security sits next to the code that does the work.


6. Mobile — the headline change in Tauri 2

Tauri 1 was desktop-only. The biggest banner on v2 was iOS and Android support. In 2026, the question is how far that has actually come.

What works

  • iOS and Android builds are stable (since the 2.0 GA)
  • The same web frontend is shared between desktop and mobile
  • Rust backend logic is callable from mobile too; native plugins can be written in Swift/Kotlin/Java
  • Many of the core plugins (filesystem, notifications, dialogs) work on mobile

What is still rough

  • Desktop plugin set is not equal to mobile plugin set. Some plugins are desktop-only, some are mobile-only
  • The Tauri team itself acknowledges the "mobile as a first-class citizen" promise was overstated — they shipped the foundation and are iterating with the community
  • iOS sideloading remains constrained by Apple policy; Android is comparatively open
  • Tauri does not match Flutter or React Native on native-feeling mobile UX out of the box — being a webview, you still need to invest in mobile look-and-feel

When to choose Tauri mobile

ScenarioRecommendation
Existing Tauri desktop app, extend to mobileTauri mobile works well
Mobile-first app, desktop secondaryReact Native / Flutter / native
Desktop and mobile both first-class, code sharing mattersSeriously evaluate Tauri
Native mobile UX is the product (gestures, transitions)Native or Flutter

Summary: Tauri mobile is excellent as "the mobile companion to a desktop app." It is not yet as mature as React Native or Flutter as the foundation for a mobile-first product.


7. Electron, Wails, Neutralino, native — the comparison matrix

Each tool in its place.

PropertyTauri 2ElectronWailsNeutralinoNative
Backend languageRustNode.jsGoC/C++(core)Swift/Kotlin/C++/C#
RenderingSystem webviewBundled ChromiumSystem webviewSystem webviewNative UI
Bundle size5-40MB80-200MB10-25MB2-10MB1-20MB
Memory idle30-80MB200-400MB50-100MB30-70MB20-80MB
MobileiOS/AndroidNoneNoneNoneFirst-class
Render consistencyLow (OS-dependent)HighLowLowHigh
Learning curveHigh (Rust)Low (JS)Medium (Go)LowHighest
Security modelcapabilities (declarative)Code patternsMethod bindingsallowListOS APIs
MaturityHigh (2024 GA)Very highMediumMediumTop
EcosystemGrowing fastHugeSmallSmallOS-dependent
SponsorFoundation (independent)OpenJSCommunityCommunityPlatform vendor

Each tool in one line

  • Tauri 2: lightweight, secure desktop and mobile. Best if you are willing to learn Rust.
  • Electron: largest ecosystem, consistent rendering. Pay the weight for fast development.
  • Wails: the natural choice for Go teams. Brings Go's simplicity to desktop. Some features (multi-window etc.) still in progress.
  • Neutralino: ultra-light. No compilation, Node-like APIs. Lightest, but the least mature.
  • Native: one team per platform. Best UX, most expensive.

8. The Rust learning curve — the real cost for a frontend team

The biggest barrier to Tauri is not bundle size, nor the system webview. It is Rust. Honest accounting of the cost when a frontend-heavy team picks it up.

The good news

  • Tauri is "frontend as usual + Rust only for the backend." For a simple app with thin backend, the Rust footprint is a few IPC commands and a main file
  • The Tauri CLI generates ~90% of the boilerplate — cargo, cross-compile, code-signing wiring are largely abstracted
  • AI tooling (Claude, ChatGPT) is good at generating Rust. Simple IPC commands are mostly autocompleted
  • A thin backend rarely touches the hard parts of Rust — lifetimes, deep async, unsafe

The bad news

  • The moment the backend grows — DB pools, async work, concurrency — the Rust curve gets steep
  • Compile times. Initial build 5-15 minutes, incremental 5-30 seconds. Electron has instant reload
  • Error messages are friendly but voluminous. A one-line JS error becomes 30 lines of trait-bound explanations in Rust
  • If exactly one person on the team can review Rust, that person becomes the bottleneck
  • Dependency auditing. cargo audit is fine, but the tooling surface is thinner than the npm world, and some crates lean heavily on unsafe

A practical decision rubric

Team situationRecommendation
Frontend-only team, no Rust experience, simple backendTauri worth trying
Frontend-only team, complex backend (DB, concurrency)Electron + Node, or Tauri with sidecar
At least one Rust-experienced memberTauri strongly recommended
Systems-programming backgroundTauri feels natural
Must ship in six months, schedule tightElectron — everything is paved

A useful pattern: many teams compromise with the sidecar pattern. Keep Tauri's main app lean, spawn the heavy backend (e.g., Python ML, a Node service) as a separate process under Tauri's supervision. This minimizes the Rust learning cost while still capturing the bundle and memory wins.

Summary: Rust is a real cost for a frontend team. Bearable when the backend is simple. A different tool may be better when it isn't.


9. Apps actually built with Tauri 2

Notable Tauri 2 apps as of 2026. Names, categories, and a guess at why Tauri.

AppCategoryWhy Tauri (likely)
SilentKeysPrivacy-first voice dictationOn-device ML, small bundle, deep system integration
VoxlyAI voice dictationFast startup, low memory, system tray
Watson.aiMeeting capture and extractionBackground operation, fine-grained system permissions
PakeTurn any webpage into a desktop appSmall bundle is the value proposition (clear win over Electron)
RivetVisual programming for AI agentsIDE-class UX, memory efficiency matters
XGetterVideo / audio downloaderSystem integration, small installer
Kunobi / KubeliKubernetes desktop clientsDeveloper tooling, fast startup, MCP server built in

Patterns you can see:

  1. New projects — built on Tauri from scratch, not ports from Electron
  2. Small to medium scope — not gargantuan IDEs, but tools with a clear single purpose
  3. System integration is central — tray icons, background work, global shortcuts, fine system permissions
  4. Memory-sensitive — apps that run continuously in the background (dictation, meeting capture)
  5. Developer or power-user audience — non-trivial Linux share

Conversely, why migrations from Electron are rare:

  • The Electron codebase is a large existing asset — rewriting the backend in Rust is expensive
  • Users are already happy — don't fix what isn't broken
  • A low Linux share makes the system-webview gap easier to ignore

Summary: Tauri overwhelmingly wins as a deliberate choice in new projects. Migrations from existing Electron apps need a strong, specific motivation.


10. When to pick what — a decision tree

Start:
├─ Is mobile a primary target?
│   ├─ Yes → React Native / Flutter / native
│   └─ No → continue
├─ Is Linux a serious target?
│   ├─ Yes, 30%+ Linux users → can you absorb the system-webview gap?
│   │       ├─ Yes → Tauri / Wails
│   │       └─ No, pixel consistency required → Electron
│   └─ No or small → continue
├─ Does the team have Rust experience?
│   ├─ Yes or willing to learn → Tauri strongly recommended
│   ├─ Go experience instead → consider Wails
│   └─ JS only, no learning time → Electron
├─ Are bundle size and memory truly important?
│   ├─ Yes (background app, low-spec hardware) → Tauri
│   └─ No, users don't care → Electron ships faster
├─ Is the backend complex?
│   ├─ Yes — heavy DB, concurrency → Electron, or Tauri with sidecar
│   └─ No, mostly UI → Tauri or Neutralino
└─ Tight schedule (3-6 months)?
    ├─ Yes → Electron (everything is paved)
    └─ No, 12+ months → Tauri's learning cost amortizes

Short rules

  • New project, simple backend, bundle matters → Tauri
  • Existing Electron app, users happy → leave it
  • AAA-class desktop UX with Linux as a first-class citizen → Electron
  • Desktop + mobile code sharing matters → Tauri or Flutter
  • Go team → Wails
  • Need to claw back every megabyte → Neutralino (at the cost of maturity)
  • Per-OS best UX, unlimited headcount → native

11. Anti-patterns and pitfalls

Patterns that lead to regretting the Tauri choice.

"Write it like Electron"

  • Symptom: a single huge Rust file with every IPC command in one place
  • Result: compile times explode, every change is expensive, tests are impossible
  • Instead: split the Rust into modules (commands/, services/, models/). Keep commands thin (validate + delegate to a service).

"Turn on every capability"

  • Symptom: core:default plus every fs/shell/dialog permission with wildcards
  • Result: the core value of the security model evaporates. You inherit Electron's risk surface
  • Instead: only what is truly needed, with narrow scopes. Per-window capabilities.

"Go deep on Rust async from day one"

  • Symptom: simple IPC implemented with tokio::spawn and six channels
  • Result: learning curve explodes, deadlocks, a swamp of compile errors
  • Instead: start with async fn on Tauri's default runtime. Reach for deeper concurrency only when the problem demands it.

"Ignore the system-webview gap"

  • Symptom: develop only on macOS, test on Linux right before release
  • Result: half the UI breaks under WebKitGTK
  • Instead: CI on three OSes from week one. Visual regression on every critical screen across all targets.

"Use Rust for the frontend too"

  • Symptom: Leptos / Yew / Dioxus across the whole stack
  • Result: the Rust curve doubles; the ecosystem is one-hundredth of React's
  • Instead: stay on your usual framework (React/Vue/Svelte); Rust for the backend only. Tauri is designed for that split.

"Ask an AI agent to build the whole Tauri app"

  • Symptom: an agent ships capability configs and IPC patterns that look right but leak permissions
  • Result: too-wide permissions, missing input validation, side channels opening
  • Instead: a human reviews every capability and IPC command. Tests at the IPC boundary are mandatory, not optional.

12. Migration — when moving from Electron to Tauri

If a migration is justified, here is the sequence.

1. Measure: record current Electron bundle, memory, startup
2. Backend inventory: list every Node dependency — what maps to a Rust crate?
3. Frontend pull-out: can the UI move unchanged (usually yes)?
4. IPC mapping: every ipcMain handler maps to a Tauri command
5. Capability design: list what each IPC touches → capabilities files
6. System integration: tray / notifications / shortcuts via Tauri plugins
7. Cross-OS testing: three-OS CI on day one, visual regression
8. Re-measure: verify the promised numbers actually appear
9. Beta users → gradual rollout

Signals not to migrate

  • Many Node dependencies have no Rust equivalent (specific ML libraries, etc.)
  • 70% of users on macOS, system-webview gap is moot, memory is fine
  • Zero Rust-capable engineers, no six months to learn
  • Users are happy, no complaints about bundle size

Signals to migrate

  • A 24/7 background app whose memory is an actual user-machine burden
  • Installer size affects download conversion (low-spec, low-bandwidth markets)
  • A security audit is on the docket and cleaning up Electron's surface costs more
  • A mobile expansion is on the roadmap and you want desktop code reuse

Epilogue — checklist, anti-patterns, what's next

Tauri 2 is not "Electron made lighter." It is a different tool with different trade-offs. Accept the system-webview shadow, the Rust curve, the smaller ecosystem — get back bundle, memory, and a stronger default security model. New project, thin backend, bundle matters — Tauri is the rational choice. Existing Electron apps need a real reason before any migration.

Checklist for choosing Tauri

  1. Is this a new project or an existing migration? — new project amortizes the cost
  2. How complex is the backend? — simpler favors Tauri
  3. Mobile plans? — yes makes Tauri 2's mobile support attractive
  4. Rust experience on the team? — zero means add learning time to the schedule
  5. Linux user share? — high means budget for system-webview QA
  6. Do bundle and memory truly matter? — background apps and low-spec hardware win here
  7. Schedule pressure? — tight favors Electron; 12+ months favors Tauri
  8. Designed capabilities early? — the security model shapes the code layout
  9. Sidecar pattern needed? — heavy backends should run as a separate process
  10. Three-OS CI from week one? — discovering the gap at release is too late

Anti-patterns

Anti-patternWhy it's badInstead
Choosing Tauri only on bundle sizeIgnores Rust and QA costScore team and schedule too
1:1 porting an Electron codebase to RustRust idioms differRedesign per domain
Wildcard capabilitiesSecurity model collapsesOnly what's needed, narrow scopes
Linux testing only at the endFound right before launchThree-OS CI from week one
Rust for the frontend (Leptos etc.)One-hundredth the ecosystemFrontend as usual, Rust for backend only
One giant commands.rsCompile times explodeSplit by domain into modules
Deep async from day oneCurve becomes a cliffSync first, async when needed
Assume webview consistencyShip breaks on macOS-only reviewCSS/JS fallbacks plus visual regression on all OSes
Expect Tauri mobile to replace React NativeMobile-first UX still maturingUse as a desktop companion
Migrate without ROIWastes assets, disturbs usersOnly when measurable wins exist

What's next

Next: "Pitfalls of Rust async — async/await, tokio, Send bounds, and where lifetimes meet futures." If this post was about deciding whether to use Tauri, the next is about what you actually meet when the Tauri backend gets complex. Rust async is powerful but full of traps — Send/Sync trait bounds, the interaction between lifetimes and futures, channel choice (mpsc/oneshot/broadcast), select patterns, and how to call sync code from an async context. It applies to a Tauri backend, a backend microservice, anywhere Rust async is taken seriously.


References