← ./articles

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

References