Tauri v2 아키텍처 가이드
작성일: 2026-04-06 Last Updated: 2026-04-06 대상: Tauri v2 데스크 탑 앱 (Windows / macOS / Linux) 공통 참조:
../common/(프로젝트 구조, 보안, 인증, 테스트 등)
목차
- Tauri v2 아키텍처 개요
- IPC 통신
- Capabilities & Permissions
- Plugin 시스템
- Multi-Window / Multi-WebView
- 시스템 트레이
- 메뉴 바
- 프론트엔드 구조
- Sidecar (외부 바이너리 번들링)
1. Tauri v2 아키텍처 개요
핵심 구성요소
+-------------------------------------------------------+
| Tauri Application |
| |
| +------------------+ +----------------------+ |
| | Rust Core | | WebView (Frontend) | |
| | | IPC | | |
| | - Commands |<---->| - TypeScript/JS | |
| | - Events | | - SvelteKit/React | |
| | - State | | - HTML/CSS | |
| | - Plugins | | | |
| +------------------+ +----------------------+ |
| | | |
| +--------+--------+ +--------+--------+ |
| | OS Native APIs | | Tao (Window) | |
| | - Filesystem | | Wry (WebView) | |
| | - Network | | | |
| | - Process | | - Windows: WebView2 |
| | - Keychain | | - macOS: WKWebView |
| | - Notifications | | - Linux: WebKitGTK |
| +-----------------+ +-----------------+ |
+-------------------------------------------------------+
Tauri v1 vs v2 주요 차이
| 항목 | v1 | v2 |
|---|---|---|
| 보안 모델 | allowlist (boolean 토글) | Capabilities + Permissions + Scopes |
| 모바일 지원 | 없음 | iOS / Android 지원 |
| 플러그인 시스템 | 기본 | ACL 기반 권한 내장 |
| IPC | invoke + listen | invoke + events + channels |
| Multi-WebView | 미지원 | WebView 단위 권한 분리 |
| 빌드 시스템 | tauri.conf.json 단일 | tauri.conf.json + capabilities/*.json 분리 |
프로젝트 구조
폴더 구조 상세는
/platform/client/common/project-structure참조
tauri-app/
+-- src-tauri/ # Rust 백엔드
| +-- src/
| | +-- lib.rs # 앱 빌더 + 플러그인 등록
| | +-- commands/ # IPC 커맨드 핸들러
| | | +-- mod.rs
| | | +-- session.rs
| | | +-- filesystem.rs
| | +-- services/ # 비즈니스 로직 (Rust)
| | +-- state/ # 앱 상태 관리
| | +-- tray.rs # 시스템 트레이
| | +-- menu.rs # 메뉴 바
| +-- Cargo.toml
| +-- tauri.conf.json # Tauri 설정
| +-- capabilities/ # 권한 정의 (v2 신규)
| | +-- default.json
| | +-- admin.json
| +-- icons/ # 앱 아이콘
| +-- build.rs
+-- src/ # 프론트엔드
| +-- lib/ # SvelteKit 또는 React
| +-- routes/ # 페이지
| +-- app.html
+-- package.json
+-- vite.config.ts
+-- tsconfig.json
앱 초기화 (lib.rs)
// src-tauri/src/lib.rs
use tauri::Manager;
mod commands;
mod state;
mod tray;
mod menu;
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
// 플러그인 등록
.plugin(tauri_plugin_updater::Builder::new().build())
.plugin(tauri_plugin_shell::init())
.plugin(tauri_plugin_store::Builder::new().build())
.plugin(tauri_plugin_notification::init())
.plugin(tauri_plugin_fs::init())
.plugin(tauri_plugin_http::init())
.plugin(tauri_plugin_clipboard_manager::init())
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_process::init())
// 상태 관리
.manage(state::AppState::default())
// IPC 커맨드 등록
.invoke_handler(tauri::generate_handler![
commands::session::create_session,
commands::session::send_message,
commands::filesystem::read_config,
])
// 시스템 트레이
.setup(|app| {
tray::create_tray(app)?;
menu::setup_menu(app)?;
Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
2. IPC 통신
Tauri v2는 세 가지 IPC 메커니즘을 제공한다.
IPC 메커니즘 비교
| 메커니즘 | 방향 | 용도 | 패턴 |
|---|---|---|---|
| invoke | Frontend -> Rust | 명령 실행 + 결과 반환 | Request-Response |
| events | 양방향 | 비동기 알림, 브로드캐스트 | Pub-Sub |
| channels | Rust -> Frontend | 스트리밍 데이터 (진행률 등) | Streaming (v2 신규) |
2.1 invoke (커맨드 호출)
// src-tauri/src/commands/session.rs
use serde::{Deserialize, Serialize};
use tauri::State;
use crate::state::AppState;
#[derive(Serialize, Deserialize)]
pub struct SessionInfo {
pub session_id: String,
pub created_at: String,
}
#[tauri::command]
pub async fn create_session(
state: State<'_, AppState>,
model: String,
) -> Result<SessionInfo, String> {
let session = state
.session_manager
.lock()
.await
.create(model)
.map_err(|e| e.to_string())?;
Ok(SessionInfo {
session_id: session.id.clone(),
created_at: session.created_at.to_rfc3339(),
})
}
// src/lib/api/session.ts
import { invoke } from '@tauri-apps/api/core';
interface SessionInfo {
session_id: string;
created_at: string;
}
export async function createSession(model: string): Promise<SessionInfo> {
return await invoke<SessionInfo>('create_session', { model });
}
2.2 events (이벤트)
// Rust -> Frontend 이벤트 발행
use tauri::Emitter;
fn notify_status_change(app: &tauri::AppHandle, status: &str) {
app.emit("session-status-changed", status)
.expect("failed to emit event");
}
// 특정 윈도우에만 발행
fn notify_window(window: &tauri::WebviewWindow, payload: &str) {
window.emit("window-specific-event", payload)
.expect("failed to emit to window");
}
// Frontend에서 이벤트 수신
import { listen, emit } from '@tauri-apps/api/event';
// Rust -> Frontend 수신
const unlisten = await listen<string>('session-status-changed', (event) => {
console.log('Status:', event.payload);
});
// Frontend -> Rust 이벤트 발행
await emit('user-action', { action: 'click', target: 'button' });
// 정리 (컴포넌트 언마운트 시)
unlisten();
2.3 channels (스트리밍, v2 신규)
channels는 Rust에서 Frontend로 대량의 데이터를 스트리밍할 때 사용한다. invoke의 반환값보다 효율적이다.
// Rust: 채널로 진행률 스트리밍
use tauri::ipc::Channel;
use serde::Serialize;
#[derive(Clone, Serialize)]
#[serde(rename_all = "camelCase", tag = "event", content = "data")]
pub enum DownloadProgress {
#[serde(rename_all = "camelCase")]
Started { content_length: u64 },
#[serde(rename_all = "camelCase")]
Progress { chunk_length: u64, downloaded: u64 },
Finished,
}
#[tauri::command]
pub async fn download_file(
url: String,
on_progress: Channel<DownloadProgress>,
) -> Result<String, String> {
let total_size = 1024 * 1024; // 예시
on_progress.send(DownloadProgress::Started {
content_length: total_size,
}).map_err(|e| e.to_string())?;
let mut downloaded: u64 = 0;
// ... 다운로드 로직 ...
for chunk in chunks {
downloaded += chunk.len() as u64;
on_progress.send(DownloadProgress::Progress {
chunk_length: chunk.len() as u64,
downloaded,
}).map_err(|e| e.to_string())?;
}
on_progress.send(DownloadProgress::Finished)
.map_err(|e| e.to_string())?;
Ok("download complete".to_string())
}
// Frontend: 채널 수신
import { invoke, Channel } from '@tauri-apps/api/core';
type DownloadProgress =
| { event: 'Started'; data: { contentLength: number } }
| { event: 'Progress'; data: { chunkLength: number; downloaded: number } }
| { event: 'Finished' };
const onProgress = new Channel<DownloadProgress>();
onProgress.onmessage = (message) => {
if (message.event === 'Progress') {
const percent = (message.data.downloaded / totalSize) * 100;
updateProgressBar(percent);
}
};
await invoke('download_file', { url: 'https://...', onProgress });
IPC 설계 체크리스트
- 모든
#[tauri::command]에 입력값 검증 구현 - 에러를
Result<T, String>또는 커스텀 에러 타입으로 반환 - 무거운 작업은
async+tokio::spawn으로 비동기 처리 - 스트리밍 데이터는 events 대신 channels 사용
- 커맨드 이름은 snake_case, 프론트엔드 호출 시에도 동일
-
State<T>로 앱 상태 공유 (Mutex/RwLock 사용)
3. Capabilities & Permissions
Tauri v2의 보안 모 델은 Capabilities > Permissions > Scopes 3계층으로 구성된다.
보안 모델 구조
Capability (역할 단위)
+-- 어떤 Window/WebView에 적용?
+-- Permission (기능 단위)
+-- allow / deny (명시적)
+-- Scope (범위 제한)
+-- 허용 URL, 파일 경로 등
capabilities/ 폴더 구성
src-tauri/capabilities/
+-- default.json # 기본 윈도우 권한
+-- admin.json # 관리자 전용 권한 (확장)
+-- minimal.json # 최소 권한 (제한된 WebView)
기본 Capability 설정
// src-tauri/capabilities/default.json
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "메인 윈도우 기본 권한",
"windows": ["main"],
"permissions": [
"core:default",
"dialog:default",
"notification:default",
"clipboard-manager:allow-write",
{
"identifier": "http:default",
"allow": [
{ "url": "https://api.example.com/**" },
{ "url": "https://auth.example.com/**" }
],
"deny": [
{ "url": "http://**" }
]
},
{
"identifier": "fs:allow-read",
"allow": [
{ "path": "$APPDATA/**" },
{ "path": "$APPCONFIG/**" }
]
},
{
"identifier": "fs:allow-write",
"allow": [
{ "path": "$APPDATA/**" }
]
},
{
"identifier": "shell:allow-open",
"allow": [
{ "cmd": "open", "args": ["https://*"] }
]
}
]
}
주요 Scope 변수
| 변수 | 설명 | 예시 경로 (macOS) |
|---|---|---|
$APPDATA | 앱 데이터 폴더 | ~/Library/Application Support/{bundle_id} |
$APPCONFIG | 앱 설정 폴더 | ~/Library/Application Support/{bundle_id} |
$APPLOCALDATA | 앱 로컬 데이터 | ~/Library/Application Support/{bundle_id} |
$APPCACHE | 앱 캐시 | ~/Library/Caches/{bundle_id} |
$APPLOG | 앱 로그 | ~/Library/Logs/{bundle_id} |
$HOME | 홈 디렉토리 | ~/ |
$DESKTOP | 바탕화면 | ~/Desktop |
$DOCUMENT | 문서 폴더 | ~/Documents |
$DOWNLOAD | 다운로드 폴더 | ~/Downloads |
$TEMP | 임시 폴더 | /tmp |
권한 설정 원칙
| 원칙 | 설명 |
|---|---|
| 최소 권한 | 필요한 기능만 허용 (deny가 allow보다 우선) |
| 윈도우 분리 | 각 윈도우/WebView에 별도 Capability 적용 |
| Scope 제한 | URL, 파일 경로를 최소한으로 지정 |
| 명시적 허용 | 위험 권한(shell:allow-execute 등)은 반드시 명시적 허용 |
4. Plugin 시스템
공식 플러그인 목록
| 플러그인 | 패키지 | 용도 |
|---|---|---|
tauri-plugin-updater | @tauri-apps/plugin-updater | 자동 업데이트 |
tauri-plugin-shell | @tauri-apps/plugin-shell | 쉘 명령/sidecar 실행 |
tauri-plugin-store | @tauri-apps/plugin-store | Key-Value 영구 저장소 |
tauri-plugin-fs | @tauri-apps/plugin-fs | 파일시스템 접근 |
tauri-plugin-http | @tauri-apps/plugin-http | HTTP 클라이언트 |
tauri-plugin-notification | @tauri-apps/plugin-notification | OS 알림 |
tauri-plugin-dialog | @tauri-apps/plugin-dialog | 파일 선택/저장 다이얼로그 |
tauri-plugin-clipboard-manager | @tauri-apps/plugin-clipboard-manager | 클립보드 |
tauri-plugin-process | @tauri-apps/plugin-process |