Next.js中ReferenceError: localStorage is not defined问题求助
Next.js迁移中localStorage未定义错误解决方案
将React项目迁移到Next.js,完成pages路由和store.js重构后,出现错误:ReferenceError: localStorage is not defined,错误发生在reducers/auth.js的initialState初始化阶段,具体代码位置如下:
19 | const initialState = { >20 | access: localStorage.getItem('access'), | ^ 21 | refresh: localStorage.getItem('refresh'), 22 | isAuthenticated: null, 23 | user: null
相关代码文件
reducers/auth.js
import { LOGIN_SUCCESS, LOGIN_FAIL, SIGNUP_SUCCESS, SIGNUP_FAIL, ACTIVATION_SUCCESS, ACTIVATION_FAIL, USER_LOADED_SUCCESS, USER_LOADED_FAIL, AUTHENTICATED_SUCCESS, AUTHENTICATED_FAIL, PASSWORD_RESET_SUCCESS, PASSWORD_RESET_FAIL, PASSWORD_RESET_CONFIRM_SUCCESS, PASSWORD_RESET_CONFIRM_FAIL, LOGOUT } from '../actions/types'; const initialState = { access: localStorage.getItem('access'), refresh: localStorage.getItem('refresh'), isAuthenticated: null, user: null }; export default function (state = initialState, action) { const { type, payload } = action; switch(type) { case AUTHENTICATED_SUCCESS: return { ...state, isAuthenticated: true } case LOGIN_SUCCESS: localStorage.setItem('access', payload.access); localStorage.setItem('refresh', payload.refresh); return { ...state, isAuthenticated: true, access: payload.access, refresh: payload.refresh } case USER_LOADED_SUCCESS: return { ...state, user: payload } case SIGNUP_SUCCESS: return { ...state, isAuthenticated: false } case AUTHENTICATED_FAIL: return { ...state, isAuthenticated: false } case USER_LOADED_FAIL: return { ...state, user: null } case LOGIN_FAIL: case SIGNUP_FAIL: case LOGOUT: localStorage.removeItem('access'); localStorage.removeItem('refresh'); return { ...state, access: null, refresh: null, isAuthenticated: false, user: null } case PASSWORD_RESET_SUCCESS: case PASSWORD_RESET_FAIL: case ACTIVATION_SUCCESS: case ACTIVATION_FAIL: case PASSWORD_RESET_CONFIRM_SUCCESS: case PASSWORD_RESET_CONFIRM_FAIL: return { ...state } default: return state } };
src/store.js
import { useMemo } from 'react'; import { applyMiddleware } from 'redux'; import { legacy_createStore as createStore } from 'redux'; import { composeWithDevTools } from 'redux-devtools-extension'; import thunkMiddleware from 'redux-thunk'; import reducers from './reducers'; let store; function initStore(initialState) { return createStore( reducers, initialState, composeWithDevTools(applyMiddleware(thunkMiddleware)) ); }; export const initializeStore = (preloadedState) => { let _store = store ?? initStore(preloadedState); if (preloadedState && store) { _store = initStore({ ...store.getState(), ...preloadedState, }) store = undefined; } if (typeof window === 'undefined') return _store; if (!store) store = _store; return _store; } export function useStore(initialState) { const store = useMemo(() => initializeStore(initialState), [initialState]); return store; }
问题原因
Next.js支持服务端渲染(SSR),在服务端执行代码时,浏览器的window对象和localStorage API不存在,直接调用会抛出未定义错误。
解决方案
方案一:修改initialState,添加环境判断
直接在reducers/auth.js的initialState中判断当前是否为客户端环境,仅在客户端时读取localStorage:
const initialState = { access: typeof window !== 'undefined' ? localStorage.getItem('access') : null, refresh: typeof window !== 'undefined' ? localStorage.getItem('refresh') : null, isAuthenticated: null, user: null };
同时,为了避免其他action中操作localStorage时在服务端报错,给所有localStorage操作添加环境判断:
// LOGIN_SUCCESS案例 case LOGIN_SUCCESS: if (typeof window !== 'undefined') { localStorage.setItem('access', payload.access); localStorage.setItem('refresh', payload.refresh); } return { ...state, isAuthenticated: true, access: payload.access, refresh: payload.refresh } // LOGIN_FAIL、SIGNUP_FAIL、LOGOUT案例 case LOGIN_FAIL: case SIGNUP_FAIL: case LOGOUT: if (typeof window !== 'undefined') { localStorage.removeItem('access'); localStorage.removeItem('refresh'); } return { ...state, access: null, refresh: null, isAuthenticated: false, user: null }
方案二:在store初始化时注入客户端localStorage值
这种方式更符合Next.js的SSR逻辑,服务端初始化store时不涉及localStorage,仅在客户端初始化时注入localStorage数据:
- 修改
reducers/auth.js的initialState为默认值:
const initialState = { access: null, refresh: null, isAuthenticated: null, user: null };
- 修改
src/store.js的initializeStore函数,在客户端环境下读取localStorage并作为预加载状态传入:
export const initializeStore = (preloadedState) => { let clientPreloadedState = preloadedState; // 仅在客户端时获取localStorage数据 if (typeof window !== 'undefined') { clientPreloadedState = { ...clientPreloadedState, auth: { access: localStorage.getItem('access'), refresh: localStorage.getItem('refresh'), isAuthenticated: null, user: null } }; } let _store = store ?? initStore(clientPreloadedState); if (clientPreloadedState && store) { _store = initStore({ ...store.getState(), ...clientPreloadedState, }) store = undefined; } if (typeof window === 'undefined') return _store; if (!store) store = _store; return _store; }
同样,action中的localStorage操作也需要添加环境判断,参考方案一的修改。
内容的提问来源于stack exchange,提问作者Moammer
相关产品推荐
相关产品推荐

