✍️ Next.js 实战
🎯 目标:实现 Redux 在服务端渲染(SSR)中的安全注入、状态脱水与复用
🧠 关键词:Redux Toolkit、Next.js、SSR、hydrate、脱水/注水、状态隔离
🔍 为什么 SSR 场景下 Redux “容易出坑”?
在服务端渲染框架(如 Next.js)中:
- 你要在服务端生成页面(含状态) →
getServerSideProps - 再把状态注入给前端 →
window.__PRELOADED_STATE__ - 前端拿到这个 state 之后,还要接着使用 Redux…
这涉及 3 个问题:
- 🧠 服务端如何创建和使用 Redux store?
- 💾 如何将服务端生成的 state 注入给前端?
- 🔁 前端如何“接手”这个状态并复用?
✅ 实战一:服务端创建 Redux store + 获取初始状态
我们封装一个 makeStore() 工厂函数:
// store.ts
import { configureStore } from '@reduxjs/toolkit'
import rootReducer from './slices'
export const makeStore = (preloadedState = {}) => {
return configureStore({
reducer: rootReducer,
preloadedState
})
}
在 getServerSideProps 中使用:
// pages/index.tsx
import { makeStore } from '../store'
export const getServerSideProps = async () => {
const store = makeStore()
// 触发初始化 action
store.dispatch(fetchUserData())
store.dispatch(fetchPosts())
return {
props: {
preloadedState: store.getState()
}
}
}
💧 实战二:脱水 + 注水机制(dehydrate → hydrate)
脱水(服务端生成状态对象)
return {
props: {
preloadedState: store.getState()
}
}
注水(前端接管初始状态)
import { makeStore } from '../store'
export function useHydratedStore(preloadedState) {
const storeRef = useRef<any>()
if (!storeRef.current) {
storeRef.current = makeStore(preloadedState)
}
return storeRef.current
}
🧪 实战三:Next.js 页面中接入 Redux 并复用状态
import { Provider } from 'react-redux'
import { useHydratedStore } from '../utils/useHydratedStore'
export default function Page({ preloadedState }) {
const store = useHydratedStore(preloadedState)
return (
<Provider store={store}>
<PageContent />
</Provider>
)
}
⚠️ 状态隔离问题与解决方案
问题:
服务端渲染每个请求都应拥有独立 Redux store,否则状态会跨用户污染。
正确做法:
- 不要把
store声明为单例 - 使用
makeStore()工厂函数 - 服务端每次请求都重新调用
makeStore()
✅ 这样你每个请求都有自己的 store → 不会串数据、不泄露用户状态。
🎯 完整架构流程图
[Client Request]
↓
[Next.js getServerSideProps]
↓
[创建 store + dispatch 初始化 action]
↓
[store.getState() → 脱水]
↓
[传递给页面 props]
↓
[前端 useHydratedStore(preloadedState)]
↓
[Redux 正常运行 + 保持一致]
🧩 Redux Toolkit 的优势:无需再手写样板代码
使用 Redux Toolkit,可以:
- 避免手动 combineReducers
- 自动注入默认中间件(thunk、devtools)
- 类型系统自动联动
- 兼容 preloadedState 机制
const store = configureStore({
reducer: { ... },
preloadedState
})
🔐 推荐封装模式:高阶组件形式封装 Redux 注水
export function withRedux(Component) {
return function Wrapper({ preloadedState, ...props }) {
const store = useHydratedStore(preloadedState)
return (
<Provider store={store}>
<Component {...props} />
</Provider>
)
}
}
这样在任何 SSR 页面中都可以简洁使用:
export default withRedux(Page)
🧪 示例:用户登录数据 SSR 注入 Redux 流程
// getServerSideProps
const user = await fetchLoginStatus(ctx.req.cookies.token)
store.dispatch(setUser(user))
// 页面中使用 useSelector
const user = useSelector(state => state.user)
- 服务端生成:store.dispatch(setUser())
- 注水给前端:store.getState() → preloadedState
- 前端 UI 立即感知 user,无需再发请求
🔚 总结:SSR 中正确使用 Redux 的核心理念
| 要素 | 最佳实践 |
|---|---|
| 状态初始化位置 | getServerSideProps 中 dispatch |
| 状态脱水方式 | store.getState() |
| 注水方式 | preloadedState → configureStore |
| 状态隔离 | 每次请求都调用 makeStore() |
| UI 接入 Redux | 使用 <Provider store={...}> 包裹 |
⏭️ 下一篇预告
下一篇将聚焦 AI:
番外篇 4:《Redux + AI:状态生成、流转预测与智能助手实践》
你将学会:
- 用 LLM 自动生成 reducer + action
- 构建一个 ChatGPT 风格的“状态调试助手”
- 使用状态流日志训练你的智能模型,预测用户操作路径