Tauri hat sich als ernsthafter Electron-Konkurrent etabliert — mit Rust-Backend, nativen WebViews und Bundle-Größen unter 10 MB. In diesem Guide zeigen wir, wie Claude Code den Tauri-Workflow revolutioniert: von der initialen Projektstruktur über IPC-Kommunikation bis hin zu Auto-Updater und Code Signing für Windows, macOS und Linux.
1. Tauri Setup & Projekt-Struktur
Der Einstieg in Tauri beginnt mit einem klaren Setup. Claude Code kennt die Tauri-CLI, die Vite-Integration und die typische Ordnerstruktur. Statt Dokumentation zu durchsuchen, delegiert man das Bootstrapping direkt an Claude.
Voraussetzungen
Rust (rustup), Node.js 20+, und die plattformspezifischen Build-Dependencies (WebView2 auf Windows, libwebkit2gtk auf Linux, Xcode auf macOS).
Projekt initialisieren
# Claude Code Prompt:
# "Erstelle ein neues Tauri-Projekt mit Vite + TypeScript Frontend"
# Terminal-Output nach Claude Code Ausführung:
npm create tauri-app@latest my-desktop-app
✔ Choose which language to use for your frontend · TypeScript / JavaScript
✔ Choose your package manager · npm
✔ Choose your UI template · Vite
✔ Choose your UI flavor · TypeScript
# Ins Projektverzeichnis wechseln und Dependencies installieren:
cd my-desktop-app
npm install
npm run tauri dev
Projekt-Struktur
my-desktop-app/
├── src/ # TypeScript Frontend (Vite)
│ ├── main.ts # Entry Point
│ ├── App.tsx # Root Component
│ └── styles.css
├── src-tauri/ # Rust Backend
│ ├── src/
│ │ ├── main.rs # Tauri App Entry
│ │ ├── lib.rs # Commands & State
│ │ └── commands/ # Modularisierte Commands
│ │ ├── mod.rs
│ │ ├── filesystem.rs
│ │ └── database.rs
│ ├── Cargo.toml # Rust Dependencies
│ ├── tauri.conf.json # Tauri Konfiguration
│ └── icons/ # App Icons (alle Größen)
├── package.json
├── vite.config.ts
└── index.html
tauri.conf.json — Kern-Konfiguration
{
"productName": "MyDesktopApp",
"version": "1.0.0",
"identifier": "com.mycompany.mydesktopapp",
"build": {
"frontendDist": "../dist",
"devUrl": "http://localhost:5173",
"beforeDevCommand": "npm run dev",
"beforeBuildCommand": "npm run build"
},
"app": {
"windows": [
{
"label": "main",
"title": "MyDesktopApp",
"width": 1200,
"height": 800,
"minWidth": 800,
"minHeight": 600,
"resizable": true,
"fullscreen": false,
"decorations": true
}
],
"security": {
"csp": "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'"
}
},
"bundle": {
"active": true,
"targets": "all",
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
]
}
}
Claude Code Tipp: "Erkläre mir die Tauri Security-Konfiguration und erstelle eine CSP-Policy für meine App, die externe API-Calls zu api.myservice.com erlaubt." — Claude generiert direkt die korrekte CSP-Zeile und erklärt jeden Abschnitt.
Development-Workflow
# Development mit Hot-Reload (Frontend + Rust)
npm run tauri dev
# Production Build für alle Plattformen
npm run tauri build
# Nur für spezifische Plattform bauen
npm run tauri build -- --target x86_64-pc-windows-msvc
npm run tauri build -- --target aarch64-apple-darwin
npm run tauri build -- --target x86_64-unknown-linux-gnu
Pro-Tipp: Claude Code kann die gesamte src-tauri/ Ordnerstruktur auf einmal generieren — inkl. Command-Module, Cargo.toml mit allen Dependencies und einer vollständig konfigurierten tauri.conf.json. Einfach den gewünschten Feature-Scope beschreiben.
2. Rust Commands & IPC-Kommunikation
Das Herzstück von Tauri ist die IPC-Brücke zwischen TypeScript-Frontend und Rust-Backend. Commands sind typsichere Funktionen, die der WebView über invoke() aufruft. Claude Code beherrscht das Pattern perfekt.
Rust IPC
Rust Command definieren (src-tauri/src/lib.rs)
use tauri::command;
use serde::{Deserialize, Serialize};
// Typen für strukturierte Daten
#[derive(Debug, Serialize, Deserialize)]
pub struct FileInfo {
pub name: String,
pub size: u64,
pub path: String,
pub is_directory: bool,
}
// Einfacher Command ohne Parameter
#[tauri::command]
pub fn greet(name: &str) -> String {
format!("Hallo, {}! Von Rust.", name)
}
// Async Command mit strukturiertem Return
#[tauri::command]
pub async fn get_file_info(path: String) -> Result<FileInfo, String> {
let metadata = std::fs::metadata(&path)
.map_err(|e| e.to_string())?;
Ok(FileInfo {
name: std::path::Path::new(&path)
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("")
.to_string(),
size: metadata.len(),
path: path.clone(),
is_directory: metadata.is_dir(),
})
}
// Command mit mehreren Parametern
#[tauri::command]
pub async fn write_file(
path: String,
content: String,
append: bool,
) -> Result<(), String> {
use std::io::Write;
let mut file = if append {
std::fs::OpenOptions::new()
.append(true)
.open(&path)
.map_err(|e| e.to_string())?
} else {
std::fs::File::create(&path)
.map_err(|e| e.to_string())?
};
file.write_all(content.as_bytes())
.map_err(|e| e.to_string())
}
// In main.rs den invoke_handler registrieren:
fn main() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![
greet,
get_file_info,
write_file,
])
.run(tauri::generate_context!())
.expect("Fehler beim Starten der App");
}
TypeScript-Seite: invoke() aufrufen
import { invoke } from '@tauri-apps/api/core';
// Typ-Definitionen für bessere DX
interface FileInfo {
name: string;
size: number;
path: string;
is_directory: boolean;
}
// Einfacher Command-Aufruf
const greeting = await invoke<string>('greet', { name: 'Welt' });
console.log(greeting); // "Hallo, Welt! Von Rust."
// Strukturierter Return-Typ
const fileInfo = await invoke<FileInfo>('get_file_info', {
path: '/home/user/document.pdf'
});
console.log(fileInfo.name, fileInfo.size);
// Error Handling mit try/catch
async function saveFile(path: string, content: string) {
try {
await invoke('write_file', {
path,
content,
append: false
});
console.log('Datei gespeichert!');
} catch (error: unknown) {
console.error('Fehler:', error);
}
}
Events: Rust → TypeScript
// Rust: Event emittieren (z.B. für Progress-Updates)
use tauri::Manager;
#[tauri::command]
pub async fn process_large_file(
app_handle: tauri::AppHandle,
path: String,
) -> Result<(), String> {
for progress in 0..=100u8 {
app_handle.emit("file-progress", progress)
.map_err(|e| e.to_string())?;
tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
}
Ok(())
}
// TypeScript: Event empfangen
import { listen } from '@tauri-apps/api/event';
const unlisten = await listen<number>('file-progress', (event) => {
console.log(`Fortschritt: ${event.payload}%`);
updateProgressBar(event.payload);
});
// Aufräumen wenn nicht mehr benötigt
unlisten();
Claude Code Best Practice: Bitte Claude Code, TypeScript-Typen automatisch aus den Rust-Structs zu generieren. Mit dem specta-Crate kann man sogar automatische Type-Export-Skripte erzeugen lassen.
3. System-APIs & File System
Tauri bietet tiefe System-Integration: Datei-Dialoge, Benachrichtigungen, Clipboard, Shell-Befehle — alles über typsichere Plugins. Claude Code kennt alle Tauri v2 Plugins und deren korrekte Cargo.toml/tauri.conf.json Konfiguration.
System-APIs
Datei-Dialoge (dialog Plugin)
import { open, save } from '@tauri-apps/plugin-dialog';
import { readTextFile, writeTextFile } from '@tauri-apps/plugin-fs';
// Datei öffnen (einzelne Datei)
async function openDocument() {
const selected = await open({
multiple: false,
filters: [{
name: 'Dokumente',
extensions: ['txt', 'md', 'json']
}]
});
if (selected) {
const content = await readTextFile(selected as string);
displayContent(content);
}
}
// Mehrere Dateien + Ordner
async function openMultiple() {
const files = await open({
multiple: true,
directory: false,
filters: [{ name: 'Bilder', extensions: ['png', 'jpg', 'webp'] }]
});
return files as string[];
}
// Datei speichern
async function saveDocument(content: string) {
const filePath = await save({
filters: [{ name: 'Markdown', extensions: ['md'] }],
defaultPath: 'document.md'
});
if (filePath) {
await writeTextFile(filePath, content);
}
}
File System: Lesen & Schreiben
import {
readTextFile, writeTextFile,
readDir, createDir, removeFile,
exists, BaseDirectory
} from '@tauri-apps/plugin-fs';
// Datei im App-Datenverzeichnis lesen
const config = await readTextFile('config.json', {
baseDir: BaseDirectory.AppData
});
// In App-Datenverzeichnis schreiben (automatisch angelegt)
await writeTextFile('settings.json', JSON.stringify(settings), {
baseDir: BaseDirectory.AppData
});
// Verzeichnis auflisten
const entries = await readDir('documents', {
baseDir: BaseDirectory.Home
});
entries.forEach(entry => {
console.log(entry.name, entry.isDirectory ? '(Ordner)' : '(Datei)');
});
// Prüfen ob Datei existiert
const fileExists = await exists('user-data.db', {
baseDir: BaseDirectory.AppData
});
System-Benachrichtigungen
import {
isPermissionGranted,
requestPermission,
sendNotification
} from '@tauri-apps/plugin-notification';
async function showNotification(title: string, body: string) {
let permissionGranted = await isPermissionGranted();
if (!permissionGranted) {
const permission = await requestPermission();
permissionGranted = permission === 'granted';
}
if (permissionGranted) {
sendNotification({
title,
body,
icon: 'icons/128x128.png'
});
}
}
Clipboard & Shell
import { writeText, readText } from '@tauri-apps/plugin-clipboard-manager';
import { open } from '@tauri-apps/plugin-shell';
// In Clipboard kopieren
await writeText('Text in die Zwischenablage');
// Aus Clipboard lesen
const clipboardContent = await readText();
// URL im Standard-Browser öffnen
await open('https://agentic-movers.com');
// Datei im Standard-Programm öffnen
await open('/path/to/document.pdf');
Plugin-Konfiguration in Cargo.toml
[dependencies]
tauri = { version = "2", features = [] }
tauri-plugin-dialog = "2"
tauri-plugin-fs = "2"
tauri-plugin-notification = "2"
tauri-plugin-clipboard-manager = "2"
tauri-plugin-shell = "2"
tauri-plugin-updater = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tokio = { version = "1", features = ["full"] }
4. State Management & Rust Backend
Für komplexere Apps braucht man geteilten Zustand im Rust-Backend. Tauri's State-System mit Mutex und manage() ermöglicht thread-sicheres State Management — perfekt für Datenbankverbindungen, App-Konfiguration oder Background-Tasks.
Rust State
AppState mit Mutex
use std::sync::Mutex;
use tauri::State;
// State-Struktur definieren
pub struct AppState {
pub counter: Mutex<u32>,
pub config: Mutex<AppConfig>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AppConfig {
pub theme: String,
pub language: String,
pub auto_save: bool,
}
impl Default for AppConfig {
fn default() -> Self {
Self {
theme: "dark".to_string(),
language: "de".to_string(),
auto_save: true,
}
}
}
// Commands die State nutzen
#[tauri::command]
pub fn increment_counter(state: State<AppState>) -> u32 {
let mut counter = state.counter.lock().unwrap();
*counter += 1;
*counter
}
#[tauri::command]
pub fn get_config(state: State<AppState>) -> AppConfig {
state.config.lock().unwrap().clone()
}
#[tauri::command]
pub fn update_config(
state: State<AppState>,
new_config: AppConfig
) {
*state.config.lock().unwrap() = new_config;
}
State in main.rs registrieren
fn main() {
let app_state = AppState {
counter: Mutex::new(0),
config: Mutex::new(AppConfig::default()),
};
tauri::Builder::default()
.manage(app_state) // State registrieren
.plugin(tauri_plugin_fs::init())
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_notification::init())
.invoke_handler(tauri::generate_handler![
increment_counter,
get_config,
update_config,
// ... weitere Commands
])
.run(tauri::generate_context!())
.expect("Fehler beim Starten");
}
SQLite mit rusqlite
// Cargo.toml:
// rusqlite = { version = "0.31", features = ["bundled"] }
use rusqlite::{Connection, Result as SqlResult};
use std::sync::Mutex;
pub struct Database(Mutex<Connection>);
impl Database {
pub fn new(path: &str) -> SqlResult<Self> {
let conn = Connection::open(path)?;
conn.execute_batch("
CREATE TABLE IF NOT EXISTS notes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
content TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
")?;
Ok(Database(Mutex::new(conn)))
}
}
#[tauri::command]
pub async fn save_note(
db: State<'_', Database>,
title: String,
content: String,
) -> Result<i64, String> {
let conn = db.0.lock().map_err(|e| e.to_string())?;
conn.execute(
"INSERT INTO notes (title, content) VALUES (?1, ?2)",
rusqlite::params![title, content],
).map_err(|e| e.to_string())?;
Ok(conn.last_insert_rowid())
}
#[tauri::command]
pub async fn get_notes(
db: State<'_', Database>,
) -> Result<Vec<Note>, String> {
let conn = db.0.lock().map_err(|e| e.to_string())?;
let mut stmt = conn.prepare(
"SELECT id, title, content, created_at FROM notes ORDER BY created_at DESC"
).map_err(|e| e.to_string())?;
let notes: SqlResult<Vec<Note>> = stmt.query_map([], |row| {
Ok(Note {
id: row.get(0)?,
title: row.get(1)?,
content: row.get(2)?,
created_at: row.get(3)?,
})
}).map_err(|e| e.to_string())?
.collect();
notes.map_err(|e| e.to_string())
}
Claude Code Prompt-Empfehlung: "Erstelle eine vollständige CRUD-Implementierung für eine Notes-App in Tauri mit rusqlite, inklusive TypeScript-Typen und React-Hooks für alle Operationen." — Claude generiert den kompletten Stack in einem Durchgang.
5. Auto-Updater & Code Signing
Professionelle Desktop-Apps brauchen einen zuverlässigen Update-Mechanismus und Code Signing für die Distribution. Tauri liefert beides out-of-the-box — Claude Code kann die komplette CI/CD-Pipeline aufsetzen.
Auto-Updater
Updater Plugin einrichten
// In tauri.conf.json:
{
"plugins": {
"updater": {
"active": true,
"endpoints": [
"https://github.com/myorg/myapp/releases/latest/download/latest.json"
],
"dialog": false,
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXkuLi4="
}
}
}
Signatur-Schlüssel generieren
# Einmalig: Schlüsselpaar generieren
npm run tauri signer generate -- -w ~/.tauri/my-app.key
# Output:
Private key saved to ~/.tauri/my-app.key
Public key: dW50cnVzdGVkIGNvbW1lbnQ6...
# Umgebungsvariablen für CI/CD setzen:
TAURI_SIGNING_PRIVATE_KEY="$(cat ~/.tauri/my-app.key)"
TAURI_SIGNING_PRIVATE_KEY_PASSWORD="your-password"
Update-Check im TypeScript
import { check } from '@tauri-apps/plugin-updater';
import { relaunch } from '@tauri-apps/plugin-process';
async function checkForUpdates() {
try {
const update = await check();
if (update) {
console.log(`Update verfügbar: ${update.version}`);
console.log(`Aktuell: ${update.currentVersion}`);
// Download mit Fortschritt
let downloaded = 0;
let total = 0;
await update.downloadAndInstall((event) => {
switch (event.event) {
case 'Started':
total = event.data.contentLength ?? 0;
console.log(`Download gestartet: ${total} bytes`);
break;
case 'Progress':
downloaded += event.data.chunkLength;
const progress = Math.round((downloaded / total) * 100);
updateProgressUI(progress);
break;
case 'Finished':
console.log('Download abgeschlossen');
break;
}
});
// App neu starten um Update anzuwenden
await relaunch();
} else {
console.log('Kein Update verfügbar');
}
} catch (error) {
console.error('Update-Fehler:', error);
}
}
GitHub Actions CI/CD Pipeline
# .github/workflows/release.yml
name: Release
on:
push:
tags: ['v*']
jobs:
release:
strategy:
matrix:
include:
- platform: ubuntu-22.04
target: x86_64-unknown-linux-gnu
- platform: macos-latest
target: aarch64-apple-darwin
- platform: windows-latest
target: x86_64-pc-windows-msvc
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Install Linux dependencies
if: matrix.platform == 'ubuntu-22.04'
run: |
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev
- name: Build & Release
uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_KEY_PASSWORD }}
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
with:
tagName: v__VERSION__
releaseName: 'MyApp v__VERSION__'
releaseDraft: true
prerelease: false
WINDOWS
macOS
LINUX
Code Signing Übersicht
- Windows: EV Code Signing Zertifikat von DigiCert/Sectigo (ca. €200-400/Jahr) oder Azure Trusted Signing
- macOS: Apple Developer Account ($99/Jahr), notarize über
xcrun notarytool
- Linux: Kein Signing erforderlich, aber GPG-Signatur für AppImage empfohlen
6. Bundle-Optimierung vs. Electron
Der wohl wichtigste Vorteil von Tauri gegenüber Electron: die radikale Reduktion der Bundle-Größe. Während Electron eine vollständige Chromium-Instanz mitliefert, nutzt Tauri die native WebView des Betriebssystems.
Bundle-Größe
Direktvergleich: Tauri vs. Electron
| Kriterium |
Tauri 2.x (2026) |
Electron 33.x (2026) |
| Installer-Größe (Windows) |
~4–8 MB |
~120–180 MB |
| Installer-Größe (macOS) |
~6–10 MB |
~130–200 MB |
| Installer-Größe (Linux) |
~3–7 MB |
~100–160 MB |
| RAM (Idle) |
~30–60 MB |
~150–250 MB |
| Startup-Zeit |
<500 ms |
1–3 Sekunden |
| Backend-Sprache |
Rust (Memory-Safe) |
Node.js (JavaScript) |
| WebView-Engine |
WRY (systemativ) |
Chromium (gebündelt) |
| Security Model |
Strict CSP + ACL |
Konfigurierbar |
| Lernkurve |
Rust erforderlich |
Nur JavaScript |
| Plugin-Ökosystem |
Wächst schnell |
Sehr groß (npm) |
| Cross-Compilation |
Gut (GitHub Actions) |
Gut |
| Update-Mechanismus |
Built-in + signiert |
electron-updater |
Tauri Bundle-Optimierung
# Cargo.toml — Release-Profil optimieren
[profile.release]
opt-level = "z" # Für minimale Größe (vs. "3" für Speed)
lto = true # Link-Time Optimization
codegen-units = 1 # Bessere Optimierung, längerer Build
panic = "abort" # Kleinere Binary (kein Stack-Unwinding)
strip = true # Debug-Symbole entfernen
# vite.config.ts — Frontend optimieren
import { defineConfig } from 'vite';
export default defineConfig({
build: {
// Code-Splitting für kleinere Chunks
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
}
}
},
// Tree-Shaking aggressiv
minify: 'terser',
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true
}
},
// Inline-Limite für Assets
assetsInlineLimit: 4096,
}
});
WRY WebView: Plattform-Unterschiede
Tauri nutzt WRY (Web Rendering Library), eine Rust-Abstraktion über native WebViews:
- WINDOWS WebView2 (Chromium-based, Teil des Systems seit Windows 11/Update)
- macOS WKWebView (WebKit, Apple-nativ, sehr performant)
- LINUX WebKitGTK (webkit2gtk, Abhängigkeit zur Laufzeit erforderlich)
Achtung: Unterschiedliche CSS/JS-Unterstützung möglich — immer auf allen Plattformen testen!
Tauri ACL — Capability System
// src-tauri/capabilities/default.json
// Nur explizit erlaubte APIs sind zugänglich — Security by Default
{
"identifier": "default",
"description": "Standard Capabilities",
"windows": ["main"],
"permissions": [
"core:default",
"fs:allow-read-text-file",
"fs:allow-write-text-file",
"fs:allow-app-read-recursive",
"fs:allow-app-write-recursive",
"dialog:allow-open",
"dialog:allow-save",
"notification:allow-is-permission-granted",
"notification:allow-request-permission",
"notification:allow-send-notification",
"clipboard-manager:allow-write-text",
"clipboard-manager:allow-read-text",
"updater:allow-check",
"updater:allow-download-and-install"
]
}
Claude Code + Tauri: Das optimale Paar
Claude Code übernimmt die Rust-Komplexität: Es generiert typsichere Commands, erklärt Ownership-Konzepte, debuggt Mutex-Deadlocks und optimiert das Cargo.toml automatisch. Für Entwickler ohne Rust-Erfahrung ist das ein Game-Changer — man fokussiert sich auf die App-Logik, Claude auf die Rust-Spezifika.
Fazit & Empfehlung
Tauri ist 2026 die erste Wahl für neue Cross-Platform Desktop Apps, wenn man:
- Kleine Bundle-Größen braucht (10 MB vs. 200 MB bei Electron)
- Memory-Effizienz wichtig ist (30-60 MB vs. 150-250 MB RAM)
- Security ernst nimmt (ACL, CSP, Memory-Safe Rust)
- Native Performance für Backend-Operationen benötigt
Electron bleibt sinnvoll, wenn man ein riesiges npm-Ökosystem braucht, kein Rust lernen möchte oder in ein bestehendes Electron-Projekt integriert. Mit Claude Code als Rust-Copilot sinkt die Einstiegshürde für Tauri aber dramatisch.
Nächste Schritte: Im Claude Code Mastery Kurs führt das Desktop-Modul durch ein vollständiges Tauri-Projekt — von der ersten Zeile bis zum signierten Installer auf allen drei Plattformen. Inklusive Live-Debugging von Rust-Problemen mit Claude Code.
Desktop-Modul im Kurs
Im Claude Code Mastery Kurs: vollständiges Tauri-Modul mit Rust Commands, System-APIs, Auto-Updater und Code Signing für cross-platform Desktop-Anwendungen.
14 Tage kostenlos testen →