Smithery Logo
MCPsSkillsDocsPricing
Login
Smithery Logo

Accelerating the Agent Economy

Resources

DocumentationPrivacy PolicySystem Status

Company

PricingAboutBlog

Connect

© 2026 Smithery. All rights reserved.

    funnyhust

    tauri-v2

    funnyhust/tauri-v2
    Coding
    23 installs

    About

    SKILL.md

    Install

    Install via Skills CLI

    or add to your agent
    • Claude Code
      Claude Code
    • Codex
      Codex
    • OpenClaw
      OpenClaw
    • Cursor
      Cursor
    • Amp
      Amp
    • GitHub Copilot
      GitHub Copilot
    • Gemini CLI
      Gemini CLI
    • Kilo Code
      Kilo Code
    • Junie
      Junie
    • Replit
      Replit
    • Windsurf
      Windsurf
    • Cline
      Cline
    • Continue
      Continue
    • OpenCode
      OpenCode
    • OpenHands
      OpenHands
    • Roo Code
      Roo Code
    • Augment
      Augment
    • Goose
      Goose
    • Trae
      Trae
    • Zencoder
      Zencoder
    • Antigravity
      Antigravity
    ├─
    ├─
    └─

    About

    Guide for building professional desktop apps with Tauri 2.0, Rust backend, and MVVM React frontend. Use when creating cross-platform apps with Vite + React + Zustand + Tailwind CSS 4.

    SKILL.md

    Tauri 2.0 Desktop App Development

    This skill provides guidance for building professional cross-platform desktop applications using Tauri 2.0 with a Rust backend and modern React frontend following MVVM architecture.

    When to Use This Skill

    • Building cross-platform desktop apps (Windows, macOS, Linux)
    • Migrating from Electron to Tauri for smaller bundle size
    • Creating secure, performant native apps with web technologies
    • Implementing complex state management between Rust and React

    [!CAUTION] This skill is for Tauri 2.0 only. Tauri 1.x uses different APIs and configuration.

    Prerequisites

    • Rust: Install via rustup
    • Node.js: 18+ LTS
    • Platform tools:
      • macOS: Xcode Command Line Tools
      • Windows: Visual Studio Build Tools + WebView2
      • Linux: webkit2gtk, libayatana-appindicator

    Project Setup

    Quick Start

    # Create new project with React + TypeScript
    npm create tauri-app@latest my-app -- --template react-ts
    cd my-app
    
    # Install frontend dependencies
    npm install zustand react-router-dom
    npm install tailwindcss @tailwindcss/vite -D
    
    # Run development
    npm run tauri dev
    

    Tailwind CSS 4 Setup

    // vite.config.ts
    import { defineConfig } from "vite";
    import react from "@vitejs/plugin-react";
    import tailwindcss from "@tailwindcss/vite";
    
    export default defineConfig({
      plugins: [react(), tailwindcss()],
    });
    
    /* src/styles/globals.css */
    @import "tailwindcss";
    
    @theme {
      --color-primary: oklch(0.6 0.2 250);
      --color-secondary: oklch(0.7 0.15 180);
      --font-sans: "Inter", system-ui, sans-serif;
    }
    

    Rust Backend Architecture

    Module Organization

    src-tauri/src/
    ├── main.rs              # Entry point (minimal)
    ├── lib.rs               # App builder, state/plugin registration
    ├── commands/            # Tauri commands by feature
    │   ├── mod.rs
    │   ├── file.rs
    │   └── settings.rs
    ├── services/            # Business logic (pure Rust)
    │   ├── mod.rs
    │   └── storage.rs
    ├── models/              # Data structures
    │   └── mod.rs
    ├── state/               # App state management
    │   └── mod.rs
    ├── plugins/             # Custom Tauri plugins
    │   └── mod.rs
    └── error.rs             # Custom error types
    

    Command Patterns

    // commands/file.rs
    use tauri::State;
    use crate::{state::AppState, error::AppError};
    
    #[tauri::command]
    pub async fn read_file(
        path: String,
        state: State<'_, AppState>,
    ) -> Result<String, String> {
        std::fs::read_to_string(&path)
            .map_err(|e| format!("Failed to read file: {}", e))
    }
    
    #[tauri::command]
    pub async fn save_file(
        path: String,
        content: String,
    ) -> Result<(), String> {
        std::fs::write(&path, &content)
            .map_err(|e| format!("Failed to save file: {}", e))
    }
    

    Error Handling

    // error.rs
    use serde::Serialize;
    
    #[derive(Debug, Serialize)]
    pub enum AppError {
        Io(String),
        Database(String),
        Validation(String),
        NotFound(String),
    }
    
    impl From<std::io::Error> for AppError {
        fn from(err: std::io::Error) -> Self {
            AppError::Io(err.to_string())
        }
    }
    
    // Convert to Tauri invoke error
    impl From<AppError> for tauri::ipc::InvokeError {
        fn from(err: AppError) -> Self {
            tauri::ipc::InvokeError::from(serde_json::to_string(&err).unwrap())
        }
    }
    

    State Management

    // state/mod.rs
    use std::sync::Mutex;
    use serde::{Deserialize, Serialize};
    
    #[derive(Default)]
    pub struct AppState {
        pub settings: Mutex<AppSettings>,
        pub cache: Mutex<Vec<String>>,
    }
    
    #[derive(Default, Serialize, Deserialize, Clone)]
    pub struct AppSettings {
        pub theme: String,
        pub language: String,
    }
    
    // lib.rs - Register state
    pub fn run() {
        tauri::Builder::default()
            .manage(AppState::default())
            .invoke_handler(tauri::generate_handler![
                commands::file::read_file,
                commands::file::save_file,
            ])
            .run(tauri::generate_context!())
            .expect("error while running tauri application");
    }
    

    Permissions & Security

    Capabilities (Tauri 2.0)

    Tauri 2.0 uses a capability-based security model. Define permissions in src-tauri/capabilities/:

    // src-tauri/capabilities/default.json
    {
      "$schema": "../gen/schemas/desktop-schema.json",
      "identifier": "default",
      "description": "Default capabilities for the app",
      "windows": ["main"],
      "permissions": [
        "core:default",
        "fs:default",
        "dialog:default",
        "shell:allow-open"
      ]
    }
    

    Permission Scopes

    // Fine-grained file system access
    {
      "permissions": [
        {
          "identifier": "fs:allow-read",
          "allow": [
            { "path": "$APPDATA/**" },
            { "path": "$DOCUMENT/**" }
          ]
        },
        {
          "identifier": "fs:allow-write",
          "allow": [
            { "path": "$APPDATA/**" }
          ]
        }
      ]
    }
    

    Security Best Practices

    Practice Implementation
    Minimal permissions Only request what you need
    Input validation Validate all frontend data in Rust
    Path traversal prevention Use tauri::path APIs, not raw strings
    No dangerousRemoteDomainIpcAccess Avoid unless absolutely necessary
    CSP headers Configure in tauri.conf.json
    // tauri.conf.json - Security settings
    {
      "app": {
        "security": {
          "csp": "default-src 'self'; img-src 'self' data: https:; style-src 'self' 'unsafe-inline'"
        }
      }
    }
    

    Plugins

    Official Plugins

    Install via npm + Cargo:

    # Dialog plugin
    npm install @tauri-apps/plugin-dialog
    cargo add tauri-plugin-dialog -F tauri-plugin-dialog/unstable
    
    # File system plugin
    npm install @tauri-apps/plugin-fs
    cargo add tauri-plugin-fs
    
    # Store plugin (persistent storage)
    npm install @tauri-apps/plugin-store
    cargo add tauri-plugin-store
    

    Register Plugins

    // lib.rs
    pub fn run() {
        tauri::Builder::default()
            .plugin(tauri_plugin_dialog::init())
            .plugin(tauri_plugin_fs::init())
            .plugin(tauri_plugin_store::Builder::default().build())
            .invoke_handler(tauri::generate_handler![/* commands */])
            .run(tauri::generate_context!())
            .expect("error while running tauri application");
    }
    

    Using Plugins in Frontend

    // Dialog
    import { open, save } from '@tauri-apps/plugin-dialog';
    
    const filePath = await open({
      multiple: false,
      filters: [{ name: 'Text', extensions: ['txt', 'md'] }],
    });
    
    // File system
    import { readTextFile, writeTextFile } from '@tauri-apps/plugin-fs';
    
    const content = await readTextFile(filePath);
    await writeTextFile(filePath, newContent);
    
    // Store (persistent key-value)
    import { Store } from '@tauri-apps/plugin-store';
    
    const store = await Store.load('settings.json');
    await store.set('theme', 'dark');
    const theme = await store.get<string>('theme');
    

    Custom Plugin

    // plugins/mod.rs
    use tauri::{
        plugin::{Builder, TauriPlugin},
        Runtime,
    };
    
    pub fn init<R: Runtime>() -> TauriPlugin<R> {
        Builder::new("my-plugin")
            .invoke_handler(tauri::generate_handler![plugin_command])
            .build()
    }
    
    #[tauri::command]
    fn plugin_command() -> String {
        "Hello from plugin!".into()
    }
    

    Build & Distribution

    Development

    npm run tauri dev           # Hot-reload development
    npm run tauri dev -- --release  # Test release build
    

    Production Build

    npm run tauri build         # Build for current platform
    

    Build Configuration

    // tauri.conf.json
    {
      "productName": "My App",
      "version": "1.0.0",
      "identifier": "com.mycompany.myapp",
      "build": {
        "beforeBuildCommand": "npm run build",
        "beforeDevCommand": "npm run dev",
        "devUrl": "http://localhost:5173",
        "frontendDist": "../dist"
      },
      "bundle": {
        "active": true,
        "icon": [
          "icons/32x32.png",
          "icons/128x128.png",
          "icons/icon.icns",
          "icons/icon.ico"
        ],
        "macOS": {
          "minimumSystemVersion": "10.13"
        },
        "windows": {
          "certificateThumbprint": null,
          "timestampUrl": ""
        }
      }
    }
    

    Platform-Specific Builds

    # Cross-compile (requires toolchain)
    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
    

    Auto-Updater

    npm install @tauri-apps/plugin-updater
    cargo add tauri-plugin-updater
    
    // lib.rs
    .plugin(tauri_plugin_updater::Builder::default().build())
    
    // capabilities/default.json
    {
      "permissions": ["updater:default"]
    }
    

    Frontend Architecture (MVVM)

    Folder Structure

    src/
    ├── main.tsx                 # Entry point
    ├── App.tsx                  # Router setup
    ├── router/                  # Route definitions
    │   └── index.tsx
    ├── views/                   # View layer (pages)
    │   ├── Home/
    │   │   ├── index.tsx
    │   │   └── HomeView.tsx
    │   └── Settings/
    │       └── index.tsx
    ├── viewmodels/              # ViewModel layer (hooks)
    │   ├── useHomeViewModel.ts
    │   └── useSettingsViewModel.ts
    ├── models/                  # Model layer (types)
    │   └── index.ts
    ├── stores/                  # Zustand stores
    │   └── useAppStore.ts
    ├── services/                # Tauri bridge
    │   └── tauriService.ts
    ├── components/              # Reusable UI
    │   └── Button/
    ├── hooks/                   # Custom hooks
    └── styles/
        └── globals.css
    

    MVVM Pattern

    Layer Responsibility Example
    Model Data types, stores models/, stores/
    View UI rendering (dumb) views/, components/
    ViewModel Logic, state binding viewmodels/ hooks

    Zustand Store

    // stores/useAppStore.ts
    import { create } from 'zustand';
    import { persist, createJSONStorage } from 'zustand/middleware';
    
    interface AppState {
      theme: 'light' | 'dark';
      sidebarOpen: boolean;
      setTheme: (theme: 'light' | 'dark') => void;
      toggleSidebar: () => void;
    }
    
    export const useAppStore = create<AppState>()(
      persist(
        (set) => ({
          theme: 'dark',
          sidebarOpen: true,
          setTheme: (theme) => set({ theme }),
          toggleSidebar: () => set((s) => ({ sidebarOpen: !s.sidebarOpen })),
        }),
        {
          name: 'app-storage',
          storage: createJSONStorage(() => localStorage),
        }
      )
    );
    

    Tauri Bridge Service

    // services/tauriService.ts
    import { invoke } from '@tauri-apps/api/core';
    
    export const tauriService = {
      async readFile(path: string): Promise<string> {
        return invoke<string>('read_file', { path });
      },
    
      async saveFile(path: string, content: string): Promise<void> {
        return invoke('save_file', { path, content });
      },
    
      async getSettings(): Promise<AppSettings> {
        return invoke<AppSettings>('get_settings');
      },
    };
    

    ViewModel Hook

    // viewmodels/useHomeViewModel.ts
    import { useState, useEffect, useCallback } from 'react';
    import { tauriService } from '../services/tauriService';
    import { useAppStore } from '../stores/useAppStore';
    
    export function useHomeViewModel() {
      const [files, setFiles] = useState<string[]>([]);
      const [loading, setLoading] = useState(false);
      const [error, setError] = useState<string | null>(null);
      const { theme } = useAppStore();
    
      const loadFiles = useCallback(async () => {
        setLoading(true);
        setError(null);
        try {
          const data = await tauriService.listFiles();
          setFiles(data);
        } catch (e) {
          setError(e instanceof Error ? e.message : 'Unknown error');
        } finally {
          setLoading(false);
        }
      }, []);
    
      useEffect(() => {
        loadFiles();
      }, [loadFiles]);
    
      return { files, loading, error, theme, loadFiles };
    }
    

    View Component

    // views/Home/HomeView.tsx
    import { useHomeViewModel } from '../../viewmodels/useHomeViewModel';
    
    export function HomeView() {
      const { files, loading, error, loadFiles } = useHomeViewModel();
    
      if (loading) return <div className="animate-pulse">Loading...</div>;
      if (error) return <div className="text-error">{error}</div>;
    
      return (
        <div className="p-4">
          <h1 className="text-2xl font-bold text-primary">Files</h1>
          <ul className="mt-4 space-y-2">
            {files.map((file) => (
              <li key={file} className="p-2 bg-surface rounded">
                {file}
              </li>
            ))}
          </ul>
          <button onClick={loadFiles} className="mt-4 btn-primary">
            Refresh
          </button>
        </div>
      );
    }
    

    React Router Setup

    // router/index.tsx
    import { createBrowserRouter, RouterProvider } from 'react-router-dom';
    import { lazy, Suspense } from 'react';
    
    const Home = lazy(() => import('../views/Home'));
    const Settings = lazy(() => import('../views/Settings'));
    
    const router = createBrowserRouter([
      { path: '/', element: <Home /> },
      { path: '/settings', element: <Settings /> },
    ]);
    
    export function AppRouter() {
      return (
        <Suspense fallback={<div>Loading...</div>}>
          <RouterProvider router={router} />
        </Suspense>
      );
    }
    

    Decision Tree

    What do you need?
    ├── Create new project
    │   └── npm create tauri-app@latest -- --template react-ts
    ├── Add Rust command
    │   └── Create in commands/, register in lib.rs
    ├── Add plugin
    │   ├── Official → npm install + cargo add
    │   └── Custom → Create in plugins/
    ├── Manage permissions
    │   └── Edit capabilities/*.json
    ├── Manage frontend state
    │   └── Use Zustand stores/
    ├── Call Rust from React
    │   └── Use tauriService bridge
    └── Build for production
        └── npm run tauri build
    

    Common Pitfalls

    Issue Solution
    Commands not found Register in generate_handler![]
    Permission denied Add to capabilities/*.json
    State not updating Check Mutex lock is released
    Build fails on CI Install platform dependencies
    Large bundle size Enable strip and lto in Cargo.toml

    Resources

    Examples

    • Project Structure - Recommended folder layout
    • Rust Backend:
      • Commands - Command patterns
      • State - State management
      • Error Handling - Error types
    • React Frontend:
      • MVVM Structure - Architecture guide
      • Zustand Patterns - Store patterns
      • Tauri Hooks - Custom hooks

    Templates

    • tauri.conf.json - Config template

    External Resources

    • Tauri 2.0 Docs
    • Tauri Plugins
    Recommended Servers
    InstantDB
    InstantDB
    Astro Docs
    Astro Docs
    Svelte
    Svelte
    Repository
    funnyhust/poc_kaenx_creator
    Files