You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何useLocalStorage在Next.js中无法正常使用?

Next.js 中 useLocalStorage 触发 Hydration 失败的原因与解决办法

问题现象

使用第三方useLocalStorage Hook初始化状态时,会触发Next.js的 hydration 错误:

Error: Hydration failed because the initial UI does not match what was rendered on the server.

但换成原生useState初始化相同状态则完全正常:

// 触发错误的代码
const [selectedOrganizationShortId, setSelectedOrganizationShortId] = useLocalStorage<string>('teamId', undefined)
// 正常运行的代码
const [selectedOrganizationShortId, setSelectedOrganizationShortId] = useState<string>(undefined)
const [selectedProgramId, saveSelectedProgramId] = useState<string | undefined>(undefined)

原因分析

Next.js 采用服务端渲染(SSR)或静态生成(SSG)模式时,页面会先在服务端完成首次渲染,再在客户端进行hydration(注水)激活交互。

第三方useLocalStorage的实现逻辑会直接访问浏览器的localStorage对象,但服务端环境中根本不存在localStorage,这就导致:

  • 服务端渲染时,Hook只能返回预设的默认值
  • 客户端hydration时,Hook会从localStorage读取实际存储的值

两者初始状态不一致,最终触发hydration不匹配的错误。而原生useState的初始值在服务端和客户端完全相同,不会出现这个问题。

解决办法

1. 确保useLocalStorage仅在客户端执行

针对Next.js不同路由模式,有两种实现方式:

Pages Router 场景

用useEffect延迟读取localStorage,让服务端先以默认值完成渲染,客户端再更新状态:

import { useState, useEffect } from 'react';

const [selectedOrganizationShortId, setSelectedOrganizationShortId] = useState<string>(undefined);

useEffect(() => {
  // 仅在客户端执行时读取localStorage
  const savedId = localStorage.getItem('teamId');
  if (savedId) {
    setSelectedOrganizationShortId(savedId);
  }
}, []);
App Router 场景

先加上'use client'指令标记组件为客户端组件,再封装一个适配Next.js的useLocalStorage Hook(避免服务端访问localStorage):

'use client';
import { useState, useEffect } from 'react';

function useLocalStorage<T>(key: string, initialValue: T) {
  // 初始值先使用传入的默认值,保证服务端和客户端一致
  const [storedValue, setStoredValue] = useState<T>(initialValue);

  useEffect(() => {
    try {
      // 客户端环境下读取localStorage并更新状态
      const item = window.localStorage.getItem(key);
      if (item) {
        setStoredValue(JSON.parse(item) as T);
      }
    } catch (error) {
      console.error('读取localStorage失败:', error);
    }
  }, [key]);

  const setValue = (value: T | ((val: T) => T)) => {
    try {
      const valueToStore = typeof value === 'function' ? (value as (val: T) => T)(storedValue) : value;
      setStoredValue(valueToStore);
      window.localStorage.setItem(key, JSON.stringify(valueToStore));
    } catch (error) {
      console.error('写入localStorage失败:', error);
    }
  };

  return [storedValue, setValue] as const;
}

// 使用示例
const [selectedOrganizationShortId, setSelectedOrganizationShortId] = useLocalStorage<string>('teamId', undefined);

2. 强制服务端与客户端初始状态一致

如果需要在组件初始化时直接使用localStorage的值,可以先判断当前环境是否为客户端,避免服务端执行相关逻辑:

const initialValue = typeof window !== 'undefined' ? localStorage.getItem('teamId') : undefined;
const [selectedOrganizationShortId, setSelectedOrganizationShortId] = useState<string | null>(initialValue);

内容的提问来源于stack exchange,提问作者János

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 17:15:35