Static Assets
How images, fonts, CSS, videos, and other static files are embedded in a Tauri v2 app and served to the React frontend via the asset protocol.
Static assets are files that never change during runtime: images, fonts, CSS files, audio, video, and other media. In a Tauri app that uses React and Vite, these files get compiled into the final executable and are served through a built-in web protocol so the WebView can use them without relying on the local filesystem.
A Tauri application bundles its entire frontend—including every imported image, font, and stylesheet—directly into the binary. When your React code references an asset, it does not read a file from disk. It asks the WebView to load it from an internal asset:// address that Tauri generates. This is why you never need to worry about file paths breaking after installation, and why an app can run portably without extra folders.
How Vite and Tauri work together
When you run npm run tauri build, two things happen in sequence. First, Vite processes the React project and outputs optimized files into a dist folder — the path Tauri reads from build.frontendDist. Every asset that you import with an import statement gets a hashed filename and lands in that directory. Files placed in the public folder are copied as-is. Then Tauri takes that dist folder and embeds it into the Rust binary.
During development, Vite’s dev server handles assets and Tauri’s development proxy forwards requests to it. In a production build, there is no dev server; the embedded assets are served by Tauri’s custom protocol, which answers URLs that look like asset://localhost/.... The frontend does not notice any difference—a relative URL like /logo.png or an imported ./assets/hero.jpg just works in both environments.
Asset protocol basics:
Tauri registers an internal protocol (asset) so that the WebView can fetch embedded files. The default configuration allows all bundled frontend assets. If your app needs to display files that live outside the binary (e.g., a photo the user selected), you must enable the asset protocol scope and use the convertFileSrc helper. This section focuses on assets that ship with the application.
Types of static assets
Every file that your React app references without generating it at runtime is a static asset. The most common categories are:
- Images: PNG, JPEG, SVG, WebP, GIF, ICO, AVIF, and others.
- Fonts: WOFF, WOFF2, TTF, OTF, EOT.
- CSS files: Both global stylesheets and CSS modules.
- Videos and audio: MP4, WebM, OGG, MP3, WAV, FLAC, AAC.
- Other files: JSON data, PDF documents, text files, markdown, or even shader source code.
All of these are handled by Vite’s asset pipeline and become part of the Tauri binary.
Importing assets in React components
Using Vite’s import syntax
The standard way to include an image, font, or any other file is to import it directly in a JavaScript or TypeScript module. Vite will process the import and return the final URL that the browser—or in this case, the WebView—should use.
import heroImage from './assets/hero.jpg';
import './styles/global.css';
export default function App() {
return (
<div>
<img src={heroImage} alt="Hero banner" />
</div>
);
}
During a production build, the import resolves to something like /assets/hero.a1b2c3d4.jpg. Because Tauri embeds the entire dist directory, this path automatically maps to the asset protocol and the image loads instantly. The hash in the filename also guarantees cache-busting: when you change the image, the filename changes and the WebView fetches the new version without manual intervention.
Using the public directory
Files placed in the public folder are not processed by Vite. They are copied into the build output unchanged and served at the root path. This is convenient for files that you want to reference with a predictable, static URL—for example a favicon.ico or a downloadable PDF.
public/
favicon.ico
privacy-policy.pdf
In your React code you reference them with an absolute path starting from the root:
<link rel="icon" href="/favicon.ico" />
<a href="/privacy-policy.pdf" download>Download Policy</a>
Because they are not hashed, be careful with caching. If the file changes but the name stays the same, a user might see a stale version until the WebView cache clears.
Public folder caching:
Files in public are served with their original names. If you update a file without renaming it, returning users may not see the new content. For assets that change frequently, prefer Vite imports so the filename hash automatically invalidates the cache.
Importing fonts
Font files follow the same rules. You can import them in CSS with @font-face and let Vite resolve the path, or put them in public and reference them with a root-relative URL. The import-based approach is usually better because it works across all deployment scenarios.
@font-face {
font-family: 'Inter';
src: url('../assets/fonts/Inter-Regular.woff2') format('woff2');
font-weight: 400;
font-style: normal;
}
Import that CSS file in your main entry point or component, and the font will be embedded and served correctly.
Importing audio and video
Media files can be imported and used as src attributes just like images:
import backgroundMusic from './assets/background.mp3';
export default function MusicPlayer() {
return <audio src={backgroundMusic} controls />;
}
For large videos, embedding directly in the binary can cause build failures or slow down the app. Tauri provides a separate mechanism—Resources—for files that are too large to embed comfortably.
Large assets and the binary size limit
Every static asset you import through Vite becomes part of the binary. A few hundred kilobytes of images is fine; a 4 GB video is not. When Tauri tries to embed a massive file, the Rust compiler can run out of memory during the build, resulting in an LLVM ERROR: out of memory crash.
Out-of-memory builds:
Embedding files larger than a few hundred megabytes directly into the frontend bundle is likely to crash the build. This is not a Vite limitation—it is a consequence of the Rust compiler linking all embedded data into a single executable.
For large static assets that are still essential to the application (e.g., HD video files, offline databases), use the Resources system instead. Resources are declared in tauri.conf.json and are bundled alongside the binary without being linked into it at compile time. They can then be loaded through the asset protocol or Tauri’s command APIs. Refer to the Resources section for a complete walkthrough.
Choose the right tool:
If a file is under a few megabytes and changes only when the app updates, import it through Vite. If it is larger or needs to be writable at runtime, use Tauri Resources or external file APIs instead.
Verifying that assets load correctly
After a production build, you can confirm that static assets are being served from the embedded binary. Run the built application and open the developer tools (if enabled). Inspect the Network tab or the element’s src attribute. You should see URLs that begin with asset://localhost or https://asset.localhost (depending on the platform and Tauri version). A successfully loaded image will show a status 200 and render properly in the WebView.
Correct asset protocol URL:
Seeing asset://localhost/assets/hero.a1b2c3d4.jpg in the source confirms that the image is embedded and served from the binary—not from a local file path. The app will behave identically on every machine.
Common mistakes
Trying to read embedded assets from the filesystem
Tauri embeds frontend assets inside the binary. They do not exist as separate files on disk. Calling Node.js fs.readFileSync('/assets/logo.png') or using a Rust command to read the file will fail because there is no file at that path. Always use the URL that Vite or the asset protocol provides.
Hardcoding absolute filesystem paths
The frontend runs inside a sandboxed WebView. An <img src="C:\Users\name\image.png"> will not work—the WebView cannot access arbitrary filesystem paths. If you need to display a file that lives on the user’s disk, use the asset protocol scope and the convertFileSrc helper from @tauri-apps/api.
Over-embedding large files
As described above, pushing everything into the binary leads to compile failures and bloated downloads. Separate small, immutable assets (Vite imports) from large or updatable assets (Resources, sidecars, or remote URLs).
Ignoring the asset protocol scope when loading external files
The default asset protocol scope does not allow serving arbitrary paths from disk. If you try to load a file chosen by the user without configuring the scope, you will see an error like “asset protocol not configured to allow the path.” The Security & Capabilities chapter explains how to set up the scope properly.
Summary
Static assets are the most common kind of embedded file in a Tauri project. They work because Vite and Tauri’s asset protocol collaborate to make them available without any runtime file-reading code.