Common Configuration Mistakes
How to identify and fix frequent configuration errors in Tauri v2 projects using React and Vite
Configuration in Tauri v2 is spread across several files — tauri.conf.json, capability files, Cargo.toml, and sometimes Vite’s own config. When one of these is slightly off, the result can be a cryptic build failure, a broken feature at runtime, or a security hole that goes unnoticed until it is too late.
The mistakes that follow are the ones that developers running React + Vite with Tauri hit most often. Each section names the mistake, explains why it breaks things, shows the incorrect configuration alongside the correct one, and gives you a concrete way to verify that the fix works.
Frontend Path Misconfiguration
Tauri needs to know where your built frontend lives so it can bundle it into the final application. With React + Vite, the default build output goes to a folder called dist at the project root. If the path in tauri.conf.json points somewhere else, the build will either fail or package an empty page.
Pointing frontendDist to the Wrong Directory
The build.frontendDist field tells Tauri which folder contains the production build of your frontend. A Vite project typically places compiled HTML, JavaScript, and assets inside ../dist — that is, a dist folder one level up from src-tauri. See Build Configuration for the rest of the build object.
A common error is setting this path to ../build, ./dist, or something similar that does not match what Vite actually outputs.
{
"build": {
// ❌ Wrong — Vite doesn't output to this folder by default
"frontendDist": "../build"
}
}
Tauri will try to find an index.html inside ../build. When it cannot, the build aborts with an error like failed to find index.html.
The fix is to point frontendDist to the folder Vite actually creates.
{
"build": {
"frontendDist": "../dist"
}
}
Custom Vite Output Directories:
If you have changed Vite’s build.outDir in vite.config.ts, you must update frontendDist to match. Tauri does not read your Vite config — it trusts tauri.conf.json absolutely.
To verify the path after fixing it, run npm run build from the frontend root, then check that the dist folder appears. Run cargo tauri build --debug from src-tauri and confirm the bundled application opens.
Mixing Up devUrl and frontendDist
Tauri uses two different entry points depending on whether you are in development or building for production.
build.devUrlis the URL Tauri’s webview loads duringtauri dev. With Vite, this is typicallyhttp://localhost:5173.build.frontendDistis the folder Tauri packages when you runtauri build.
A new project generated with create-tauri-app sets both values correctly. Problems arise when someone changes the Vite dev server port (in vite.config.ts) but forgets to update devUrl, or when they overwrite one value during manual editing.
{
"build": {
// ❌ Vite is running on port 3000, but devUrl still points to 5173
"devUrl": "http://localhost:5173",
"frontendDist": "../dist"
}
}
With this mismatch, tauri dev opens the webview and tries to connect to port 5173. If Vite is actually on port 3000, the webview shows a connection refused error — a blank white screen that gives no clue about the real cause.
The correct configuration locks both values to what your development server actually uses.
{
"build": {
"devUrl": "http://localhost:3000",
"frontendDist": "../dist"
}
}
Quick Sanity Check:
After running tauri dev, open the same URL in a regular browser. If it loads your React app there, the webview will load it too. If the browser cannot connect, neither can Tauri.
Missing or Misplaced Resources
Tauri lets you ship extra files — images, binaries, configuration files — inside the application bundle by listing them in the bundle.resources array. Paths in that array are relative to the src-tauri directory. A mistake in those paths often goes undetected during development because tauri dev serves resources from disk, but tauri build cannot find them.
Resources Referenced with Incorrect Relative Paths
Imagine you have a SQLite database file you want to embed at src-tauri/data/app.db. A common misconfiguration is to write the path as if it were relative to the frontend root.
{
"bundle": {
"resources": [
// ❌ This resolves from src-tauri, not from the project root
"src-tauri/data/app.db"
]
}
}
Tauri looks for src-tauri/src-tauri/data/app.db, which does not exist. The build fails with a file-not-found error inside the bundle step.
The path must be relative to src-tauri.
{
"bundle": {
"resources": [
"data/app.db"
]
}
}
Glob Patterns Require Care:
When using glob patterns like images/*.png, remember the base is src-tauri. A pattern of *.png will match only .png files directly inside src-tauri, not inside a subfolder unless you explicitly include the folder.
To confirm resources are bundled correctly, look inside the generated application bundle after a build. On macOS, right-click the .app and choose “Show Package Contents”; on Windows, navigate to the installed directory. Your files should be present.
Sidecar Binaries with Mismatched Names
External binaries (sidecars) are declared in tauri.conf.json under bundle.externalBin and are expected to live in src-tauri/binaries by default. See Adding External Binaries for the target-triple naming rules. The name must include the platform‑specific suffix (.exe on Windows) and match the exact filename of the binary you place there.
A developer might name the binary ffmpeg in the config but place ffmpeg-x86_64-unknown-linux-gnu in the folder, or forget to include the .exe extension for Windows.
{
"bundle": {
"externalBin": [
// ❌ Tauri won't find this on Windows
"ffmpeg"
]
}
}
The sidecar API call in Rust will return a “sidecar not found” error at runtime, but the build itself may succeed. The developer sees a broken feature and spends hours debugging the sidecar spawning code before checking the config.
The fix is to list the exact filenames as they appear in the binaries directory. You can use the target_os triple placeholders to let Tauri auto-select the correct binary, or list each platform variant explicitly.
{
"bundle": {
"externalBin": [
"ffmpeg-x86_64-unknown-linux-gnu",
"ffmpeg-x86_64-pc-windows-msvc.exe",
"ffmpeg-aarch64-apple-darwin"
]
}
}
Placeholders Are Safer:
Using the built-in {target-triple} placeholder (e.g., ffmpeg-{target-triple}) reduces the chance of a mismatch. Tauri replaces it with the architecture it is building for, so you only need one entry.
Permissions and Capability Errors
Tauri v2 moved permission management into capability files. Every plugin and core API your app uses must be explicitly granted in a capability file, or the API call fails silently.
Capability Files Not Referenced or Misnamed
A capability file located at src-tauri/capabilities/desktop.json must be declared in the app.capabilities array of tauri.conf.json. Omitting that reference is like never creating the file.
{
"app": {
// ❌ The capability file exists but Tauri doesn't know about it
"capabilities": []
}
}
All plugin APIs and commands listed in desktop.json become unavailable. The frontend code that calls them receives a permission error in the browser console, but only at runtime — the build does not warn about missing capabilities.
Add the capability file name (without .json) to the array.
{
"app": {
"capabilities": ["desktop"]
}
}
Confirming Permissions Are Active:
Run the app with tauri dev and open the developer console. If you see a message like Permission denied for 'fs:read-all', a capability is missing. Once the capability is in place, the message disappears and the API call works.
Plugin Permissions Listed but the Plugin Not Installed
A developer can copy capability snippets from documentation that include permissions like "fs:default" without adding the corresponding plugin to Cargo.toml and registering it in Rust. Tauri ignores unknown permissions, so no error appears. The frontend API call simply returns a “not allowed” error at runtime.
For example, the filesystem plugin requires the tauri-plugin-fs crate and an initialization call in the Rust backend. Without both, even a perfect capability file does nothing.
// ❌ Missing plugin registration
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
The fix is to add the plugin crate to Cargo.toml, run cargo update, and then initialize it in the builder.
[dependencies]
tauri-plugin-fs = "2"
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_fs::init())
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
The Registration Chain:
A capability file grants permission to a plugin, but the plugin still needs to be compiled into the binary and attached to the Tauri app. It is a three-part chain: Cargo dependency, Rust registration, and capability declaration. A break in any link causes a silent failure.
Content Security Policy Too Restrictive
Tauri v2 applies a default Content Security Policy that allows loading resources from tauri://localhost, ipc://localhost, and https://tauri.localhost. If your React app fetches data from an external API or uses inline styles, the CSP can block those requests.
A common mistake is to modify build.devUrl or add resources without adjusting the CSP, then seeing a blank screen in production where the app worked fine in development. Development mode (tauri dev) uses a different security context, so CSP violations are only visible during tauri build.
{
"app": {
"security": {
"csp": "default-src 'self'; style-src 'self' 'unsafe-inline'"
}
}
}
The above CSP allows inline styles but blocks connections to any external API. If your React app calls https://api.example.com, the browser blocks the fetch and logs a CSP violation.
To fix this, add the API origin to connect-src. Do not use default-src * — open up only what your app genuinely needs.
{
"app": {
"security": {
"csp": "default-src 'self'; connect-src 'self' https://api.example.com; style-src 'self' 'unsafe-inline'"
}
}
}
Disabling CSP Is Not a Fix:
Setting "csp": null removes all protections and makes your app an easy target. Always craft a policy that covers exactly what your frontend loads, and test it with a production build before shipping.
Build Configuration Issues
The bundle configuration in tauri.conf.json contains identifiers, version numbers, and signing information that must match platform requirements. An error here prevents the build from completing or produces an installer that does not run.
Wrong or Missing Bundle Identifier
The bundle.identifier field is a unique string that identifies your application on the operating system. On macOS, it must follow reverse‑domain notation (e.g., com.mycompany.myapp). On Windows, it influences the installer GUID. Leaving it empty or using something like MyApp can cause the macOS build to reject the bundle.
{
"bundle": {
// ❌ Missing reverse-domain format
"identifier": "myapp"
}
}
Set a proper identifier before attempting any build.
{
"bundle": {
"identifier": "com.example.my-tauri-app"
}
}
Consistency Across Platforms:
Use the same identifier for all platforms unless you have a specific reason to differ. Tauri uses it for deep linking, storage paths, and update mechanisms. Changing it later can break existing user data paths.
Signing Identity Not Configured on macOS
macOS requires all applications to be signed for distribution, even if you do not plan to put them on the App Store. If you run tauri build on macOS without setting a signing identity in tauri.conf.json and without having a developer certificate in your keychain, the build will fail with a code signing error.
{
"bundle": {
"macOS": {
"signingIdentity": null
}
}
}
Set signingIdentity to the name of your certificate as it appears in Keychain Access, or use - to enable ad‑hoc signing for local testing.
{
"bundle": {
"macOS": {
"signingIdentity": "-"
}
}
}
Ad‑hoc Signing Is Not for Distribution:
A dash (-) tells macOS to sign the binary with a temporary identity that only works on the machine that built it. You cannot distribute an ad‑hoc signed app to other users. For distribution, obtain a certificate from Apple Developer Program and reference it by its full name.
Incorrect Installer Type on Windows
Tauri v2 supports both NSIS and WiX for Windows installers. The choice is controlled by the bundle.windows.wix object. If wix is missing or misconfigured, Tauri falls back to NSIS. But some features — like multi‑language installers or custom UI — require WiX, and the config must reflect that.
A developer who needs WiX may forget to enable it.
{
"bundle": {
"windows": {
// ❌ Wix is not enabled, NSIS is used instead
}
}
}
Explicitly set the wix configuration to use WiX and supply a language array.
{
"bundle": {
"windows": {
"wix": {
"language": ["en-US"]
}
}
}
}
WiX Toolset Required:
To build a WiX installer, you must install the WiX Toolset build tools on your development machine. Tauri will error if wix is enabled but the tools are not found.
Platform-Specific Pitfalls
Each operating system imposes its own rules on application metadata, icons, and security settings. Overlooking these platform details can cause the build to succeed but the resulting application to fail when launched by a user.
Windows Icon Format and ICO Files
Windows requires the application icon to be an .ico file containing multiple resolutions (16×16, 32×32, 48×48, 256×256). Using a single-resolution .png or a .icns file meant for macOS leads to a missing icon in the taskbar and file explorer.
{
"bundle": {
"icon": [
// ❌ Windows does not display this as the app icon
"icons/icon.png"
]
}
}
Generate a proper .ico file from a source image (tools like ImageMagick or online converters can do this) and reference it.
{
"bundle": {
"icon": [
"icons/icon.ico"
]
}
}
Multiple Icon Files Are Common:
Tauri’s icon field accepts an array. You can supply one .ico for Windows, one .icns for macOS, and one .png for Linux in the same config. Tauri selects the appropriate one for each target.
macOS Hardened Runtime Entitlements
If you plan to distribute a macOS app outside the App Store, you must enable the Hardened Runtime and specify the entitlements your app needs. Without them, certain features (like accessing the webcam or microphone) will silently fail, and notarization will be rejected.
The entitlements are defined in a .plist file referenced by bundle.macOS.entitlements.
{
"bundle": {
"macOS": {
"entitlements": null
}
}
}
When entitlements is null, Tauri uses a minimal default set. If your app uses the camera, you need to create an entitlements file and list the required key.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.device.camera</key>
<true/>
</dict>
</plist>
Then reference it in tauri.conf.json.
{
"bundle": {
"macOS": {
"entitlements": "Entitlements.plist"
}
}
}
Missing Entitlements Break Notarization:
Apple’s notary service scans for the Hardened Runtime flag and the entitlements file. If the flag is off or the file is missing, notarization fails with a message about invalid provisioning. This error appears only when you upload the app to Apple, not during local testing.
Linux Desktop File Categories
On Linux, Tauri generates a .desktop file that determines where your application appears in the application menu. The default categories are ["Utility"], which is fine for many tools, but if your app is a game or a development tool, the wrong category hides it from the appropriate menu section.
{
"bundle": {
"linux": {
"desktop": {
// ❌ A video player listed as a Utility won't appear under Multimedia
"categories": ["Utility"]
}
}
}
}
Choose categories from the freedesktop.org specification that match your app’s purpose.
{
"bundle": {
"linux": {
"desktop": {
"categories": ["AudioVideo", "Player"]
}
}
}
}
Categories Affect Discoverability:
Users on GNOME, KDE, and other desktop environments rely on categories to find applications. A mismatch means your app is essentially invisible after installation, even if the binary runs perfectly from the terminal.
A Systematic Way to Catch Configuration Errors Early
Most configuration mistakes share one trait: they are detectable before the final build if you know where to look. Here are three habits that prevent the worst of them from reaching production.
- Validate with
tauri devfirst. Development mode catches frontend path issues and missing capabilities immediately. If the app works withtauri devbut breaks withtauri build, the problem is almost always in the bundle or resource configuration. - Use
cargo tauri build --debugfor test builds. Debug builds skip code optimization and signing checks, which lets you isolate pure configuration errors faster. Once the config is solid, switch to a release build. - Check the generated bundle structure. After every build, open the output folder and look for your resources, icons, and binaries. On macOS, inspect the
.appcontents; on Windows, look inside the installer directory. If a file you expected is missing, the config path is wrong.
One Config File at a Time:
When troubleshooting, change only one value between build attempts. It is easy to adjust three things at once and lose track of which change actually fixed the problem. A single edit per iteration keeps the feedback loop clear.
The configuration system in Tauri v2 is explicit by design — it does not guess your intent. That explicitness means every mistake comes from a specific mismatch between what you declared and what actually exists on disk or at runtime. Once you understand the relationship between the config files, the source directories, and the build output, the errors stop being mysterious and become easy to trace.
If you have followed the fixes here and still see an issue, the Tauri CLI’s own log output is the next tool to reach for. Running cargo tauri build -v prints detailed file resolution steps, which often reveals a path misinterpretation or a missing file that a quiet build swallowed.