Desktop & Cross-Platform Rust TypeScript Claude Code

Tauri mit Claude Code: Cross-Platform Desktop Apps 2026

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 →