Architecture
Tauri Dev/Prod Resource Resolution Pattern
Tauri Dev/Prod Resource Resolution Pattern
Problem
Tauri desktop apps need to locate bundled resources (binaries, scripts, config files) at runtime. The file layout differs between dev mode (source tree) and production (app bundle), leading to path resolution failures when a single hardcoded path is used.
Symptoms:
- Binary/script found in dev but
None/missing in production env!("CARGO_MANIFEST_DIR")works at compile time but doesn’t exist in production- Resources placed in
storage_dirthat should come from the source tree in dev
Root Cause
Tauri apps have two fundamentally different resource layouts:
| Resource Type | Dev Mode Location | Production Location |
|---|---|---|
| Binaries (ollama, uv) | src-tauri/binaries/ | App.app/Contents/Resources/binaries/ |
| Python sidecar | apps/python-sidecar/ (PyInstaller binary) | App.app/Contents/MacOS/ (Tauri externalBin) |
Using storage_dir.join("resources") for everything fails in dev (those files don’t exist there), and using
CARGO_MANIFEST_DIR fails in production (it’s a compile-time constant pointing to the build machine).
Solution
1. Centralize Path Resolution in AppPaths
Add environment-aware methods to the existing AppPaths struct:
const MANIFEST_DIR: &str = env!("CARGO_MANIFEST_DIR");
impl AppPaths { /// Bundled binaries (ollama, uv) — downloaded via dev scripts, bundled in prod. pub fn binaries_dir(&self) -> PathBuf { if self.is_dev { PathBuf::from(MANIFEST_DIR).join("binaries") } else { self.storage_dir.join("binaries") } }
}2. Use AppPaths in State Initialization
// state.rs — all paths resolved from AppPaths, not hardcodedlet binaries_dir = paths.binaries_dir();
let ollama_binary = resolve_binary_in(&binaries_dir);3. Configure Tauri Resource Bundling
// tauri.conf.json — maps source paths to bundle paths{ "bundle": { "resources": { "binaries/*": "binaries/" } }}4. Dev Scripts Populate Gitignored Directories
# tools/dev/download-uv.sh — downloads to gitignored binaries/# tools/dev/download-ollama.sh — same pattern# Both follow: detect platform → download → extract → verify → place in binaries/Implementation Notes
binaries/is gitignored — never check in large binaries. Dev scripts download them.- Graceful degradation — all resolution functions return
Option<PathBuf>or checkexists(). Missing binary = feature unavailable, not crash. - Resolution helper pattern — each module exposes a
resolve_binary_in(dir: &Path) -> Option<PathBuf>that checks existence without knowing about dev/prod. - Tests verify dev paths —
test_agent_scripts_dir_devasserts the resolved path actually contains the expected file.
Prevention
Best Practices
- Never hardcode paths — always go through
AppPathsmethods - One source of truth per resource type — binaries_dir for binaries, agent_scripts_dir for scripts
- Dev scripts mirror production — if it’s bundled in prod, there’s a download script for dev
- Test both directions — unit test that dev path resolves, integration test that the resource exists there
Warning Signs
PathBuf::from(env!("CARGO_MANIFEST_DIR"))used directly in non-paths.rs codestorage_dir.join("some_file")for a file that comes from the source tree- Binary/script works on one developer’s machine but not another’s (forgot to run download script)
References
apps/desktop/src-tauri/src/paths.rs— AppPaths with binaries_dir() and agent_scripts_dir()apps/desktop/src-tauri/src/ollama_manager.rs— resolve_binary_in() patternapps/desktop/src-tauri/tauri.conf.json— resource bundling configtools/dev/download-uv.sh,tools/dev/download-ollama.sh— dev binary download scripts
Previous
Sync Engine Safety: Cursor Advancement, Poison Pill Recovery, and Self-Update Prevention Next
Tauri Workspace Lifecycle Side-Effect Pattern
Sync Engine Safety: Cursor Advancement, Poison Pill Recovery, and Self-Update Prevention Next
Tauri Workspace Lifecycle Side-Effect Pattern
Was this page helpful?
Thanks for your feedback!