nextjs - 💡(How to fix) Fix Blog Starter Example: localStorage SSR Hydration Error with Next.js 15/React 19 [3 comments, 3 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
vercel/next.js#86040Fetched 2026-04-08 02:12:50
View on GitHub
Comments
3
Participants
3
Timeline
8
Reactions
0
Author
Timeline (top)
commented ×3closed ×1labeled ×1locked ×1

Error Message

TypeError: localStorage.getItem is not a function at src/app/_components/theme-switcher.tsx:63:22

Root Cause

Root Cause: The theme switcher component uses this code to initialize state:

Code Example

TypeError: localStorage.getItem is not a function
    at src/app/_components/theme-switcher.tsx:63:22

---

const [mode, setMode] = useState<ColorSchemePreference>(
  () =>
    ((typeof localStorage !== "undefined" &&
      localStorage.getItem(STORAGE_KEY)) ??
      "system") as ColorSchemePreference,
);

---

const [mode, setMode] = useState<ColorSchemePreference>(
  () => {
    if (
      typeof window !== "undefined" &&
      typeof localStorage !== "undefined" &&
      localStorage.getItem
    ) {
      return (localStorage.getItem(STORAGE_KEY) ?? "system") as ColorSchemePreference;
    }
    return "system";
  }
);

useEffect(() => {
  if (
    typeof window !== "undefined" &&
    typeof localStorage !== "undefined" &&
    localStorage.setItem
  ) {
    localStorage.setItem(STORAGE_KEY, mode);
  }
  updateDOM();
}, [mode]);
RAW_BUFFERClick to expand / collapse

When using the official blog-starter example with Next.js 15.0.2 and React 19, the app fails to start due to a server-side rendering (SSR) hydration error in the theme switcher component. The error message is:

TypeError: localStorage.getItem is not a function
    at src/app/_components/theme-switcher.tsx:63:22

Steps to Reproduce:

  1. Clone the official Next.js blog-starter example.
  2. Install dependencies (with Next.js 15+ and React 19).
  3. Run npm run dev.
  4. Observe the error in the terminal and browser.

Root Cause: The theme switcher component uses this code to initialize state:

const [mode, setMode] = useState<ColorSchemePreference>(
  () =>
    ((typeof localStorage !== "undefined" &&
      localStorage.getItem(STORAGE_KEY)) ??
      "system") as ColorSchemePreference,
);

This check is insufficient for SSR and newer React/Next.js versions. On the server, localStorage may be undefined or not have the expected methods, causing the error.

Why This Is a Problem:

  • New users cloning the example get a broken experience out of the box.
  • The issue is exposed by stricter SSR/hydration checks in React 19 and Next.js 15.
  • The pattern is not aligned with current Next.js best practices for client-only APIs.

Suggested Fix: I received the following fix from GitHub Copilot (AI assistant), which I tested and confirmed resolves the issue:

const [mode, setMode] = useState<ColorSchemePreference>(
  () => {
    if (
      typeof window !== "undefined" &&
      typeof localStorage !== "undefined" &&
      localStorage.getItem
    ) {
      return (localStorage.getItem(STORAGE_KEY) ?? "system") as ColorSchemePreference;
    }
    return "system";
  }
);

useEffect(() => {
  if (
    typeof window !== "undefined" &&
    typeof localStorage !== "undefined" &&
    localStorage.setItem
  ) {
    localStorage.setItem(STORAGE_KEY, mode);
  }
  updateDOM();
}, [mode]);

Transparency Note: This fix was recommended by GitHub Copilot in my local environment. I am submitting it here for review and consideration by the maintainers.

Impact:

  • Fixes SSR hydration error for new users
  • Aligns with best practices for accessing browser APIs in Next.js
  • No functional changes, only defensive programming

Environment:

  • Next.js: 15.0.2
  • React: 19.0.0-rc
  • Node: v25.1.0
  • OS: macOS

Additional Context:

  • The same pattern is used in other Next.js examples (e.g., with-supabase) to avoid SSR issues.
  • This issue is not present in older Next.js/React versions due to less strict SSR/hydration behavior.

extent analysis

TL;DR

The most likely fix is to update the theme switcher component to check for the existence of window and localStorage before attempting to access them, as shown in the suggested fix.

Guidance

  • Verify that the issue is resolved by applying the suggested fix, which checks for window and localStorage before accessing them.
  • Ensure that the useEffect hook is used to update localStorage only when the component is mounted on the client-side.
  • Review other Next.js examples that use similar patterns to avoid SSR issues and consider applying the same fix.
  • Test the updated component on different environments and React/Next.js versions to ensure compatibility.

Example

const [mode, setMode] = useState<ColorSchemePreference>(
  () => {
    if (
      typeof window !== "undefined" &&
      typeof localStorage !== "undefined" &&
      localStorage.getItem
    ) {
      return (localStorage.getItem(STORAGE_KEY) ?? "system") as ColorSchemePreference;
    }
    return "system";
  }
);

useEffect(() => {
  if (
    typeof window !== "undefined" &&
    typeof localStorage !== "undefined" &&
    localStorage.setItem
  ) {
    localStorage.setItem(STORAGE_KEY, mode);
  }
  updateDOM();
}, [mode]);

Notes

The suggested fix is specific to the theme switcher component and may need to be adapted for other components that use similar patterns. Additionally, the fix assumes that the updateDOM function is defined and works as expected.

Recommendation

Apply the suggested workaround, as it fixes the SSR hydration error and aligns with best practices for accessing browser APIs in Next.js. This fix has been tested and confirmed to resolve the issue, and it does not introduce any functional changes.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING