C.W.K.
Stream
Lesson 03 of 07 · published

Persistence with plugin-store (and Why localStorage Lies)

~14 min · tauri, store, persistence, macos

Level 0Web Tourist
0 XP0/56 lessons0/13 achievements
0/100 XP to next level100 XP to go0% complete
"It worked all through development, then a real user opened the built app and every setting was gone. Welcome to webview storage."

The Trap That Only Shows Up After You Ship

You reach for the web tool you know: localStorage (maybe behind a Zustand persist middleware). In tauri dev it round-trips perfectly — settings save and reload, no problem. Then you build a real .app, sign it, hand it to someone, and on the second launch every value is gone. Nothing in your code changed. The storage layer underneath did.

Why macOS WKWebView localStorage Isn't Durable

Here's the real reason, learned the hard way in Cinder. In dev, your frontend is served from a Vite origin (http://localhost:1420), and that keeps the WebKit data directory warm, so localStorage persists. But inside a bundled, signed macOS .app, the WKWebView data directory can reset between launches under the app's sandbox and signing posture — so localStorage is effectively ephemeral there. It's not a bug you can fix in JS; it's a property of where webview storage lives on macOS.

The Fix: Persist Through Rust

The store plugin sidesteps the whole question. Instead of trusting the webview's storage, it writes JSON through Rust into the app's config directory — on macOS, under ~/Library/Application Support/<your.identifier>/. That survives app restarts and sandbox resets because it's a real file the OS doesn't wipe. The rule that falls out: in a Tauri app, durable settings belong in the store plugin (or another Rust-side persistence path), never in webview localStorage.

Code

store plugin: persistence that actually persists·ts
import { LazyStore } from "@tauri-apps/plugin-store";

// Writes JSON through Rust to the app's config dir — survives restarts.
// On macOS: ~/Library/Application Support/<your.identifier>/settings.json
const store = new LazyStore("settings.json");

await store.set("theme", "dark");
await store.save();              // flush to disk

const theme = await store.get<string>("theme"); // "dark", even after relaunch
dev lies to you; the built app tells the truth·text
Why this matters (the Cinder reasoning, distilled):

  dev (npm run tauri dev)        built & signed .app
  ─────────────────────────      ─────────────────────────
  served from localhost:1420     no Vite origin
  WebKit data dir stays warm     WKWebView dir can RESET on relaunch
  localStorage round-trips ✓     localStorage silently empties ✗

  → settings via store plugin (Rust → config dir) survive both.

External links

Exercise

Add the store plugin and persist a setting (e.g. a theme toggle) through it. Quit and relaunch your app (in dev first) and confirm it restored. Then write one sentence predicting what would happen if you'd used localStorage in a built macOS .app instead — and why the store plugin avoids that fate.
Hint
LazyStore('settings.json'), store.set/get/save. The prediction: localStorage might survive dev but reset in the bundled .app because the WKWebView data dir isn't durable there; the store plugin writes a real file in the config dir that the OS keeps.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.