.claude/skills/tauri-2/tauri-impl/tauri-impl-project-setup/SKILL.md
Use when starting a new Tauri 2 project, choosing a frontend framework, or understanding project structure. Prevents incorrect lib.rs/main.rs split and missing build configuration that breaks dev and production builds. Covers create-tauri-app scaffolding, project structure, frontend framework integration, src-tauri layout, and source control. Keywords: tauri project setup, create-tauri-app, scaffolding, lib.rs, main.rs, frontend framework, project structure.
npx skillsauth add OpenAEC-Foundation/OpenAEC-Workspace-Composer tauri-impl-project-setupInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
| Package Manager | Command |
|----------------|---------|
| npm | npm create tauri-app@latest |
| yarn | yarn create tauri-app |
| pnpm | pnpm create tauri-app |
| bun | bunx create-tauri-app |
| cargo | cargo create-tauri-app |
my-tauri-app/
├── src/ # Frontend source (varies by framework)
│ ├── index.html
│ ├── main.ts
│ └── styles.css
├── src-tauri/ # Rust backend
│ ├── src/
│ │ ├── lib.rs # App logic (mobile entry point)
│ │ └── main.rs # Desktop entry point (calls lib.rs)
│ ├── capabilities/ # Permission capability files
│ │ └── default.json # Default window capabilities
│ ├── permissions/ # Custom command permissions (optional)
│ ├── icons/ # Application icons (all sizes)
│ ├── gen/ # Generated files (schemas, etc.)
│ │ └── schemas/
│ ├── Cargo.toml # Rust dependencies
│ ├── Cargo.lock # Deterministic build lock
│ ├── tauri.conf.json # Main configuration
│ ├── build.rs # Cargo build script
│ └── .taurignore # Exclude files from watch
├── package.json # Frontend dependencies
├── tsconfig.json # TypeScript config
└── vite.config.ts # Bundler config (if Vite)
| File | Location | Purpose |
|------|----------|---------|
| tauri.conf.json | src-tauri/ | Main app configuration |
| Cargo.toml | src-tauri/ | Rust crate dependencies |
| Cargo.lock | src-tauri/ | Deterministic dependency lock |
| default.json | src-tauri/capabilities/ | Permission grants for windows |
| build.rs | src-tauri/ | Build script (plugin permission gen) |
| package.json | root | Frontend dependencies |
ALWAYS commit src-tauri/Cargo.lock to source control -- it ensures deterministic builds across environments.
ALWAYS add src-tauri/target/ to .gitignore -- build artifacts should NEVER be committed.
NEVER put app logic in main.rs only -- ALWAYS use the lib.rs + main.rs split pattern for mobile compatibility.
NEVER forget to set a unique identifier in tauri.conf.json -- duplicate identifiers cause conflicts with other apps on the system.
ALWAYS set beforeDevCommand and beforeBuildCommand in tauri.conf.json -- without them, the frontend dev server will not start and builds will lack compiled assets.
NEVER skip adding core:default permissions in your capability file -- basic window and event operations will fail silently.
# Interactive scaffolding (recommended)
npm create tauri-app@latest
# This prompts for:
# - Project name
# - Package manager (npm, yarn, pnpm, bun, cargo)
# - Frontend language (TypeScript, JavaScript, Rust)
# - Frontend framework (React, Vue, Svelte, SolidJS, vanilla, etc.)
After scaffolding:
cd my-tauri-app
npm install # Install frontend dependencies
npm run tauri dev # Start development mode
This is the REQUIRED pattern for mobile support. Even for desktop-only apps, use this pattern to future-proof your codebase.
src-tauri/src/lib.rs -- Contains all app logic:
#[tauri::command]
fn greet(name: String) -> String {
format!("Hello, {}!", name)
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![greet])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
src-tauri/src/main.rs -- Desktop entry point (thin wrapper):
// Prevents additional console window on Windows in release
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
fn main() {
my_tauri_app_lib::run();
}
WHY this split matters: On mobile platforms (iOS/Android), there is no main() function. The #[cfg_attr(mobile, tauri::mobile_entry_point)] macro generates the platform-specific entry point on lib.rs. Without this split, your app cannot compile for mobile.
{
"$schema": "./gen/schemas/desktop-schema.json",
"identifier": "com.example.myapp",
"build": {
"devUrl": "http://localhost:5173",
"frontendDist": "../dist",
"beforeDevCommand": "npm run dev",
"beforeBuildCommand": "npm run build"
},
"app": {
"windows": [
{
"title": "My App",
"width": 800,
"height": 600
}
]
},
"bundle": {
"active": true,
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/[email protected]",
"icons/icon.icns",
"icons/icon.ico"
]
}
}
src-tauri/capabilities/default.json:
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "Default permissions for the main window",
"windows": ["main"],
"permissions": [
"core:default",
"core:window:default",
"core:webview:default"
]
}
This grants the main window access to core features. Without this, even basic window operations fail.
All frameworks follow the same integration pattern: the frontend runs its dev server, and Tauri proxies to it.
Vite (React/Vue/Svelte/SolidJS):
{
"build": {
"devUrl": "http://localhost:5173",
"frontendDist": "../dist",
"beforeDevCommand": "npm run dev",
"beforeBuildCommand": "npm run build"
}
}
Next.js (SSG mode):
{
"build": {
"devUrl": "http://localhost:3000",
"frontendDist": "../out",
"beforeDevCommand": "npm run dev",
"beforeBuildCommand": "npm run build && npm run export"
}
}
No framework (vanilla):
{
"build": {
"frontendDist": "../src"
}
}
When using vanilla HTML/CSS/JS without a dev server, omit devUrl and beforeDevCommand. Tauri serves the files directly.
package.json:
{
"devDependencies": {
"@tauri-apps/cli": "^2"
},
"dependencies": {
"@tauri-apps/api": "^2"
},
"scripts": {
"dev": "vite",
"build": "vite build",
"tauri": "tauri"
}
}
src-tauri/Cargo.toml:
[package]
name = "my-tauri-app"
version = "0.1.0"
edition = "2021"
[lib]
name = "my_tauri_app_lib"
crate-type = ["staticlib", "cdylib", "rlib"]
[build-dependencies]
tauri-build = { version = "2", features = [] }
[dependencies]
tauri = { version = "2", features = [] }
tauri-plugin-opener = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
The crate-type array is required for mobile builds: staticlib for iOS, cdylib for Android, rlib for desktop.
# Start development (hot-reload frontend + Rust backend)
npm run tauri dev
# Build for production
npm run tauri build
# Generate icons from a source image
npm run tauri icon /path/to/app-icon.png
# Generate TypeScript bindings
npm run tauri completions
# Install plugin (both Rust crate and npm package)
npm run tauri plugin add fs
# This does three things:
# 1. Adds tauri-plugin-fs to src-tauri/Cargo.toml
# 2. Adds @tauri-apps/plugin-fs to package.json
# 3. Registers the plugin in lib.rs
After installation, ALWAYS add the plugin's permissions to your capability file:
{
"permissions": [
"core:default",
"fs:default"
]
}
# Frontend build artifacts
dist/
node_modules/
# Rust build artifacts
src-tauri/target/
# Generated schemas (regenerated on build)
src-tauri/gen/
# OS-specific
.DS_Store
Thumbs.db
# IDE
.vscode/
.idea/
| File | Why |
|------|-----|
| src-tauri/Cargo.lock | Deterministic Rust builds |
| src-tauri/tauri.conf.json | App configuration |
| src-tauri/capabilities/*.json | Security permissions |
| src-tauri/icons/* | Application icons |
| package-lock.json / pnpm-lock.yaml | Deterministic JS builds |
| File/Dir | Why |
|----------|-----|
| src-tauri/target/ | Build artifacts (large, platform-specific) |
| node_modules/ | Installed packages (reproducible from lockfile) |
| dist/ | Frontend build output (regenerated) |
| Property | Type | Required | Description |
|----------|------|----------|-------------|
| devUrl | string | For dev server | URL of the frontend dev server |
| frontendDist | string | Yes | Path to compiled frontend assets |
| beforeDevCommand | string | For dev server | Command to start dev server |
| beforeBuildCommand | string | Yes | Command to build frontend |
| beforeBundleCommand | string | No | Command before bundling phase |
| features | string[] | No | Cargo feature flags to enable |
app.windows[])| Property | Type | Default | Description |
|----------|------|---------|-------------|
| label | string | "main" | Unique window identifier |
| title | string | -- | Window title bar text |
| url | string | "/" | URL or path to load |
| width / height | number | -- | Size in logical pixels |
| minWidth / minHeight | number | -- | Minimum dimensions |
| resizable | boolean | true | Allow resizing |
| fullscreen | boolean | false | Start fullscreen |
| decorations | boolean | true | Show title bar/borders |
| transparent | boolean | false | Enable transparency |
| create | boolean | true | Create at startup |
| Property | Required | Description |
|----------|----------|-------------|
| identifier | Yes | Reverse-domain notation (e.g., com.company.app) |
| productName | No | Display name of the application |
| version | No | Semver version string |
development
Use when integrating Vite with a backend framework, rendering Vite assets from server-side templates, or setting up dev/production HTML serving. Prevents incorrect manifest.json traversal and missing CSS chunk resolution in production. Covers build.manifest configuration, .vite/manifest.json structure, ManifestChunk properties, dev mode HTML setup, production rendering, CSS/JS chunk resolution, and modulepreload polyfill. Keywords: backend integration, manifest.json, ManifestChunk, Django, Laravel, Rails, modulepreload.
development
Use when encountering dev server startup failures, HMR issues, proxy errors, CORS blocks, or module not found errors during development. Prevents misconfiguring server.hmr behind reverse proxies and forgetting appType: 'custom' in middleware mode. Covers HMR full-reload debugging, proxy configuration, CORS setup, HTTPS certificates, server.fs.strict violations, port conflicts, WebSocket failures, file watcher issues, and middleware mode. Keywords: dev server, HMR, proxy, CORS, HTTPS, WebSocket, port conflict, server.fs.strict, middleware mode, file watcher.
development
Use when encountering pre-bundling errors, dependency resolution failures, stale cache issues, or slow development server startup. Prevents excluding CJS dependencies from pre-bundling (which breaks runtime module resolution) and misconfiguring optimizeDeps. Covers CJS/ESM conversion failures, missing dependency auto-discovery, optimizeDeps configuration, monorepo linked dependencies, cache invalidation, browser cache staleness, and large dependency tree performance. Keywords: pre-bundling, optimizeDeps, CJS, ESM, cache, dependency resolution, monorepo, node_modules/.vite.
development
Use when encountering Vite build failures, chunk size warnings, or version-specific build errors. Prevents the common mistake of using deprecated rollupOptions in v8 or misconfiguring build targets and minifiers. Covers Rolldown/Rollup bundling failures, CSS minification errors, sourcemap problems, library mode build failures, BundleError handling, and asset processing errors. Keywords: build error, Rolldown, chunk size, sourcemap, library mode, minify, BundleError, rollupOptions, build.target.