tauri-architecture-size-optimization
Use when shrinking a Tauri v2 release binary — tuning the Cargo release profile (`opt-level`, `lto`, `codegen-units`, `panic`, `strip`), enabling `build.removeUnusedCommands`, pruning default features on `tauri` and plugin crates, frontend tree-shaking via Vite `manualChunks` and dynamic imports, optional UPX compression with its platform caveats, and measuring with `cargo bloat` and `du`.
Tauri v2 Size Optimization
Tauri apps are already small because the WebView is OS-provided, but a default tauri build still
produces a 5–15 MB binary plus frontend assets. Most of that is reducible. This skill is the
playbook in roughly the order you should apply it — Cargo profile first (biggest win for least
effort), then feature pruning, then frontend, then UPX as a last resort.
Measure before and after each step. A du -h src-tauri/target/release/<app> after every change
keeps you honest.
1. Cargo release profile (highest ROI)
Edit src-tauri/Cargo.toml (see templates/Cargo.toml.profile-snippet):
[profile.dev]
incremental = true
[profile.release]
codegen-units = 1 # one unit → more inlining, smaller code
lto = true # link-time optimization across crates
opt-level = "s" # "s" balances size+perf; "z" is smaller but slower
panic = "abort" # strip unwinding tables (no panic = unwind support)
strip = true # remove symbols
opt-level choice:
"s"— recommended default. Size-optimized but still uses some loop/inlining heuristics."z"— most aggressive size. Disables loop vectorization. Worth trying; sometimes ~5–10% smaller, sometimes negligibly slower in practice."3"— performance, larger. Use only if you’ve measured a perf regression from"s"/"z".
lto = true (equivalent to "fat") is the right default. "thin" builds faster but optimizes
less. Only matters for release builds.
codegen-units = 1 is the single biggest size lever after LTO. It serializes codegen (slower
compile) but lets the optimizer see everything.
panic = "abort" saves ~100–300 KB by removing unwinding metadata. Cost: panics crash instead of
unwinding. For a user-facing app this is usually fine — you’d rather crash and restart than continue
in a half-broken state.
Nightly-only extras
If you build on nightly, add to [profile.release]:
trim-paths = "all" # remove embedded build paths (also privacy)
rustflags = ["-Cdebuginfo=0", "-Zthreads=8"]
trim-paths removes /Users/<you>/… strings from the binary. Privacy benefit, mild size benefit.
2. Remove unused commands
Requires Tauri 2.4+, tauri-build 2.1+, tauri-plugin 2.1+, tauri-cli 2.4+.
In src-tauri/tauri.conf.json:
{
"build": {
"removeUnusedCommands": true
}
}
The build tools generate a list of allowed commands from your capability ACL (see tauri-security),
and the generate_handler! macro drops everything not in that list. Plugins ship with many commands
you typically don’t use — this prunes them at compile time.
Typical savings: 200 KB to 1 MB depending on how many plugins are in use.
Catch: if a window’s capability later needs a command you pruned, the build fails — wire ACLs first, then enable this.
3. Prune default features
Most Tauri crates enable a lot by default. Audit each:
[dependencies]
tauri = { version = "2", default-features = false, features = ["wry"] }
tauri-plugin-fs = { version = "2", default-features = false }
tauri-plugin-http = { version = "2", default-features = false }
Safe-to-disable on tauri itself if unused: tray-icon, image-png, image-ico,
protocol-asset, devtools (release builds), macos-private-api. Test on every target OS after —
some features are platform-conditional.
Disable default features on plugins you only use one method of (e.g., tauri-plugin-http defaults
pull in cookie + redirect handling). Re-enable only what you call.
Audit unused plugins entirely. A tauri-plugin-shell in Cargo.toml you don’t actually invoke
still costs binary size.
4. Frontend tree-shaking and code-splitting
The frontend bundle ships inside the binary (or alongside it). It’s often the second-largest contributor after the Rust binary.
In vite.config.ts:
build: {
target: 'es2020',
minify: 'esbuild',
cssMinify: true,
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'], // split heavy deps
},
},
},
},
Tactics:
- Dynamic imports for routes/features used post-launch:
const settings = await import('./settings'). Keeps initial bundle small. manualChunksto split vendor code so app code changes don’t invalidate vendor cache during dev.- Drop polyfills — Tauri targets modern WebViews (WKWebView, WebView2, recent webkitgtk).
target: 'es2020'or newer is safe. - Audit with
vite build --reportorrollup-plugin-visualizerto see what’s actually shipping.
Avoid: shipping source maps in release (Vite’s default build.sourcemap: false is correct),
bundling icon fonts when SVG sprites would do, importing whole utility libraries when one function
would do (import { debounce } from 'lodash-es' not import _ from 'lodash').
See templates/tauri.conf.size.json for the size-focused Tauri config snippet.
5. UPX (last-resort compression)
UPX compresses the binary in-place. Apply it after tauri build:
upx --best --lzma src-tauri/target/release/<app>
Realistic gains: 50–70% smaller binary on disk. Caveats:
- macOS code signing breaks. UPX modifies the Mach-O; signature invalidates. You’d have to UPX then sign, but Apple’s notarization will reject UPX-packed binaries in some configurations. In practice: don’t UPX macOS release builds.
- Windows antivirus false positives. UPX-packed binaries trigger heuristics in many AV products because malware also uses UPX. Submit to Microsoft SmartScreen for reputation if you go this route.
- Linux is fine. Best target for UPX.
- Startup cost. UPX decompresses into memory at launch. Adds 50–200 ms cold-start, usually unnoticeable.
- Doesn’t actually save RAM. The binary on disk is smaller; the runtime image is the same size.
Use UPX only if disk size matters more than these costs — typically Linux distro packaging or download-size-sensitive scenarios. For a normal desktop app, skip it.
6. Measure
Don’t optimize blind. Two tools:
# Per-crate / per-function size attribution. Requires `cargo install cargo-bloat`.
cargo bloat --release --crates # what crates dominate
cargo bloat --release -n 30 # top 30 functions by size
# Just the file size, before/after each change.
du -h src-tauri/target/release/<app>
cargo bloat --crates is the highest-signal output — it tells you whether tauri, regex,
serde_json, or some plugin is your real cost center. Optimize where the bytes are.
For frontend, vite build already prints a per-chunk size report.
Realistic targets
For a small/medium Tauri 2 app with thoughtful feature pruning:
- Rust binary: 4–8 MB stripped, no UPX
- Frontend bundle: 100–500 KB gzipped
- Final installer (dmg/msi/AppImage): 5–15 MB
Below that requires either aggressive UPX (Linux only) or stripping plugins to the bone.
See also
tauri-architecture— why the binary is small to begin with (dynamic WebView linking)tauri-bundling— installer-level packaging (compression already applied by .dmg/.msi formats)tauri-security— capability ACLs that driveremoveUnusedCommandstauri-plugins— choosing which plugins to include in the first place