Skip to content

App Size

While Tauri by default provides very small binaries it doesn’t hurt to push the limits a bit, so here are some tips and tricks for reaching optimal results.

Before you can optimize your app, you need to figure out what takes up space in your app! Here are a couple of tools that can assist you with that:

  • cargo-bloat - A Rust utility to determine what takes the most space in your app. It gives you an excellent, sorted overview of the most significant Rust functions.

  • cargo-expand - Macros make your Rust code more concise and easier to read, but they are also hidden size traps! Use cargo-expand to see what those macros generate under the hood.

  • rollup-plugin-visualizer - A tool that generates beautiful (and insightful) graphs from your Vite, Rollup, or Rolldown bundle. Very convenient for figuring out what JavaScript dependencies contribute to your final bundle size the most.

  • vite-bundle-analyzer - You noticed a dependency included in your final frontend bundle, but you are unsure why? vite-bundle-analyzer renders an interactive treemap of your bundle so you can drill down into what ends up in it.

These are a couple of the tools available. Make sure to check your frontend bundler’s plugin list for more!

One of the simplest frontend agnostic size improvements you can do to your project is adding a Cargo profile to it. Why is a Rust executable large? provides an excellent explanation of why this matters and an in-depth walkthrough. At the same time, Minimizing Rust Binary Size is more up-to-date and has a couple of extra recommendations.

Dependent on whether you use the stable or nightly Rust toolchain the options available to you differ a bit. It’s recommended you stick to the stable toolchain unless you’re an advanced user.

src-tauri/Cargo.toml
[profile.release]
codegen-units = 1 # Allows LLVM to perform better optimization.
lto = true # Enables link-time-optimizations.
opt-level = "s" # Prioritizes small binary size. Use `3` if you prefer speed.
panic = "abort" # Reduces binary size by removing panic unwinding.
strip = true # Removes symbols and debuginfo from the binary.
  • codegen-units: Speeds up compile times at the cost of runtime performance.
  • lto: Enables link time optimizations.
  • opt-level: Determines the focus of the compiler. Use 3 to optimize performance, s to optimize for size, and z to also turn off loop vectorization. Results vary, so measure which one works best for your app.
  • panic: Reduce size by removing panic unwinding.
  • strip: Strip either symbols or debuginfo from a binary.
  • rpath: Assists in finding the dynamic libraries the binary requires by hard coding information into the binary.
  • trim-paths: Removes potentially privileged information from binaries.
  • rustflags: Sets Rust compiler flags on a profile by profile basis.
    • -Zthreads=8: Increases the number of threads used during compilation.

For a detailed explanation of each option and a bunch more, refer to the Cargo book’s Profiles section.

The capability system determines which commands are available to your app, and commands that are never allowed can be stripped from the binary entirely.

In Pull Request feat: add a new option to remove unused commands, we added in a new option in the tauri config file

tauri.conf.json
{
"build": {
"removeUnusedCommands": true
}
}

to remove commands that’re never allowed in your capability files (ACL), so you don’t have to pay for what you don’t use

How does it work under the hood?

tauri-cli will communicate with tauri-build and the build script of tauri, tauri-plugin through an environment variable and let them generate a list of allowed commands from the ACL, this will then be used by the generate_handler macro to remove unused commands based on that

An internal detail is this environment variable is currently REMOVE_UNUSED_COMMANDS, and it’s set to project’s directory, usually the src-tauri directory, this is used for the build scripts to find the capability files, and although it’s not encouraged, you can still set this environment variable yourself if you can’t or don’t want to use tauri-cli to get this to work (do note that as this is an implementation detail, we don’t guarantee the stability of it)

By default, Tauri uses Brotli to compress assets in the final binary. Brotli embeds a large (~120KiB) lookup table to achieve great results, but if the resources you embed are smaller than this or compress poorly, the resulting binary may be bigger than any savings.

Compression can be disabled by setting default-features to false and specifying everything except the compression feature:

[dependencies]
tauri = { version = "2", features = ["wry", "common-controls-v6", "dynamic-acl", "x11", "dbus"], default-features = false }

JavaScript makes up a large portion of a typical Tauri app, so it’s important to make the JavaScript as lightweight as possible.

You can choose from a plethora of JavaScript bundlers; the de-facto standard today is Vite, which most Tauri project templates use and which uses the Rust-based Rolldown bundler by default since Vite 8. webpack and Rollup are still common in existing projects, with Rollup’s role progressively being taken over by Rolldown, which is designed to replace it. All of them can produce minified JavaScript if configured correctly, so consult your bundler documentation for specific options. Generally speaking, you should make sure to:

This option removes unused JavaScript from your bundle. All popular bundlers enable this by default (webpack only in its production mode).

Minification removes unnecessary whitespace, shortens variable names, and applies other optimizations. Most bundlers enable this by default. Vite, for example, minifies production builds out of the box. A notable exception is Rollup, where you need plugins like @rollup/plugin-terser.

Minifiers like terser and esbuild can also be used as standalone tools.

Source maps provide a pleasant developer experience when working with languages that compile to JavaScript, such as TypeScript. As source maps tend to be quite large, you must disable them when building for production. Most bundlers, including Vite, already do this by default. They have no benefit to your end-user, so it’s effectively dead weight.

Many popular libraries have smaller and faster alternatives that you can choose from instead.

Most libraries you use depend on many libraries themselves, so a library that looks inconspicuous at first glance might add several megabytes worth of code to your app.

You can use Bundlephobia to find the cost of JavaScript dependencies. Inspecting the cost of Rust dependencies is generally harder since the compiler does many optimizations.

If you find a library that seems excessively large, Google around, chances are someone else already had the same thought and created an alternative. A good example is Moment.js and its many alternatives.

The same applies to your UI framework: if it shows up prominently in your bundle analysis, lighter alternatives such as Preact (a drop-in replacement for React) or frameworks that compile down to a minimal runtime, like Svelte and Solid, can shave a substantial amount off your bundle.

But keep in mind: The best dependency is no dependency, meaning that you should always prefer language builtins over 3rd party packages.

According to the HTTP Archive, images are the biggest contributor to website weight. So if your app includes images or icons, make sure to optimize them!

You can choose between a variety of manual options (GIMP, Photoshop, Affinity, Squoosh) or plugins for your favorite frontend build tools (vite-imagetools, vite-plugin-image-optimizer).

Do note that the imagemin library many older plugins use is officially unmaintained, so prefer plugins built on actively maintained tools such as Sharp and SVGO.

Formats such as webp and avif are considerably smaller than JPEG at comparable visual quality: WebP files are typically 25-34% smaller, and AVIF can cut the size in half. You can use tools such as Squoosh to try different formats on your images.

No one appreciates you shipping the 6K raw image with your app, so make sure to size your image accordingly. Images that appear large on-screen should be sized larger than images that take up less screen space.

In a Web Environment, you are supposed to use Responsive Images to load the correct image size for each user dynamically. Since you are not dynamically distributing images over the web, using Responsive Images only needlessly bloats your app with redundant copies.

Images that were taken straight from a camera or stock photo site often include metadata about the camera and lens model or photographer. Not only are those wasted bytes, but metadata properties can also hold potentially sensitive information such as the time, day, and location of the photo.

Consider not shipping custom fonts with your app and relying on system fonts instead.

Fonts can be pretty big, so using the fonts already included in the Operating System reduces the footprint of your app. It also avoids FOUT (Flash of Unstyled Text) and makes your app feel more “native” since it uses the same font as all other apps.

If you must include custom fonts, make sure you include them in modern formats such as woff2 as those tend to be much smaller than legacy formats. Also consider subsetting the font to the characters your app actually uses. Tools like fontTools’ pyftsubset or glyphhanger can often shrink font files by 90% or more.

Use so-called “System Font Stacks” in your CSS. There are a number of variations, but here are 3 basic ones to get you started:

Sans-Serif

font-family:
system-ui,
-apple-system,
'Segoe UI',
Helvetica,
Arial,
sans-serif,
'Apple Color Emoji',
'Segoe UI Emoji';

Serif

font-family:
Iowan Old Style,
Apple Garamond,
Baskerville,
Times New Roman,
Droid Serif,
Times,
Source Serif Pro,
serif,
Apple Color Emoji,
Segoe UI Emoji,
Segoe UI Symbol;

Monospace

font-family:
ui-monospace,
SFMono-Regular,
SF Mono,
Menlo,
Consolas,
Liberation Mono,
monospace;

The following methods involve using unstable compiler features and require the rust nightly toolchain. If you don’t have the nightly toolchain + rust-src nightly component added, try the following:

Terminal window
rustup toolchain install nightly
rustup component add rust-src --toolchain nightly

To tell Cargo that the current project uses the nightly toolchain, we will create an Override File at the root of our project called rust-toolchain.toml. This file will contain the following:

rust-toolchain.toml
[toolchain]
channel = "nightly" # Pin to a specific nightly release if you need reproducible builds, e.g. "nightly-2026-07-01"
components = ["rust-src"]
profile = "minimal"

The Rust Standard Library comes precompiled. This means Rust is faster to install, but also that the compiler can’t optimize the Standard Library. You can apply the optimization options for the rest of your binary + dependencies to the std with an unstable flag. The commands below pass the target explicitly, so know the target triple you are building for.

Terminal window
cargo tauri build --target <Target triple to build for> -- -Z build-std

If you are using panic = "abort" in your release profile optimizations, you need to make sure the panic_abort crate is compiled with std. Additionally, an extra std feature can further reduce the binary size. The following applies to both:

Terminal window
cargo tauri build --target <Target triple to build for> -- -Z build-std=std,panic_abort -Z build-std-features=panic_immediate_abort

Recent nightly toolchains are replacing the panic_immediate_abort feature with the -Cpanic=immediate-abort compiler flag, so check Minimizing Rust Binary Size for the invocation that matches your toolchain, and measure the result either way.

See the unstable documentation for more details about -Z build-std and -Z build-std-features.

UPX, Ultimate Packer for eXecutables, is a dinosaur amongst the binary packers. This well-maintained piece of kit is GPL licensed (version 2 or later) with a pretty liberal usage declaration. Our understanding of the licensing is that you can use it for any purposes (commercial or otherwise) without needing to change your license, as long as the decompression stub it embeds in your binary stays unmodified.

Maybe your target audience has very slow internet, or your app needs to fit on a tiny USB stick, and all the above steps haven’t resulted in the savings you need. Fear not, as we have one last trick up our sleeves:

UPX compresses your binary and creates a self-extracting executable that decompresses itself at runtime.

brew install upx
cargo tauri build
upx --ultra-brute src-tauri/target/release/bundle/macos/app.app/Contents/macOS/app
Ultimate Packer for eXecutables
Copyright (C) 1996 - 2018
UPX 3.95 Markus Oberhumer, Laszlo Molnar & John Reiser Aug 26th 2018
File size Ratio Format Name
-------------------- ------ ----------- -----------
963140 -> 274448 28.50% macho/amd64 app

The tips above shrink your app’s binary, but what users actually download also depends on how the app is packaged.

On Windows, the biggest factor is how the installer ships the WebView2 runtime, controlled by the bundle > windows > webviewInstallMode config. The default downloadBootstrapper keeps the installer smallest, embedBootstrapper adds around 1.8MB, and offlineInstaller embeds the entire runtime, adding around 127MB at the time of writing (the runtime grows with new Edge releases). See the WebView2 installation options in the Windows Installer guide for the trade-offs.

On Linux, the bundle format matters: an AppImage bundles your app’s shared libraries into the artifact itself, so it is considerably larger than a .deb or .rpm package of the same app.


© 2026 Tauri Contributors. CC-BY / MIT