为何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
相关产品推荐
相关产品推荐

