Guard localStorage Access in Tauri WebViews with try/catch
localStorage feels harmless until it throws during app startup.
In a Tauri app, WebView initialization, security context, storage state, and shutdown timing can differ from a normal browser tab. If your first-run check reads localStorage directly and it throws, the app can fail before the UI has a chance to recover.
Wrap storage access behind a small safe helper.
The symptom: startup fails before the app renders
The bug often appears in early UI code:
- first-run onboarding decides whether to show
- theme or layout preferences load
- a previous session flag is read
localStorage.getItem()throws synchronously- the app shows a blank screen or crashes initialization
Because the failure is synchronous, an async error boundary or later fallback may never run.
Put all reads behind a safe helper
Use a narrow wrapper:
export function getSafeStorage(key: string, defaultValue = ""): string {
try {
return localStorage.getItem(key) ?? defaultValue;
} catch (error) {
console.warn(`localStorage read failed for ${key}:`, error);
return defaultValue;
}
}
Then use it in startup code:
const onboardingShown = getSafeStorage("onboarding-shown", "false") === "true";
Now storage failure becomes "use the default," not "break the app shell."
Guard writes too
Writes can fail for quota, permissions, or teardown timing:
export function setSafeStorage(key: string, value: string): boolean {
try {
localStorage.setItem(key, value);
return true;
} catch (error) {
console.warn(`localStorage write failed for ${key}:`, error);
return false;
}
}
For low-risk preferences, returning false may be enough. For important state, surface a recoverable warning or use a Rust-backed store.
Do not store critical app state only in localStorage
localStorage is fine for:
- onboarding shown flags
- theme preference
- last selected tab
- small layout choices
It is a weak fit for:
- license state
- project data
- unsaved editor contents
- session recovery that must survive shutdown races
For important desktop-app state, prefer an explicit Tauri command that reads and writes an app data file.
Keep defaults deterministic
The fallback should be a real default, not undefined:
const theme = getSafeStorage("theme", "system");
const sidebarCollapsed = getSafeStorage("sidebar-collapsed", "false") === "true";
That makes storage failure testable. The UI should still choose one deterministic state.
Verification checklist
Test these cases:
- storage read succeeds
- storage read throws
- storage write throws
- first-run UI still renders when storage fails
- preferences fall back to documented defaults
- critical data is not stored only in localStorage