Remix + Redux SSR场景下如何同步预渲染页面的初始状态?
Remix + Redux SSR 初始状态同步实现方案
1. 创建支持预加载状态的Redux Store
先编写可接收预加载状态的Store创建函数,确保服务端和客户端能基于同一初始状态生成Store:
// app/store.ts import { configureStore } from '@reduxjs/toolkit'; import rootReducer from './reducers'; // 接收预加载状态作为参数 export function makeStore(preloadedState = {}) { return configureStore({ reducer: rootReducer, preloadedState, // 生产环境关闭devTools devTools: process.env.NODE_ENV !== 'production', }); } // 导出类型用于类型提示 export type AppStore = ReturnType<typeof makeStore>; export type RootState = ReturnType<AppStore['getState']>; export type AppDispatch = AppStore['dispatch'];
2. 在根组件Loader中获取全局初始状态
利用Remix服务端Loader获取全局通用数据(如用户信息、系统配置),将其作为初始状态传递给客户端:
// app/root.tsx import { Outlet, useLoaderData } from '@remix-run/react'; import { Provider } from 'react-redux'; import { makeStore } from './store'; // 服务端执行:获取全局初始数据 export async function loader() { // 示例:从数据库/API拉取用户信息和系统设置 const user = await fetchCurrentUser(); const appSettings = await fetchAppSettings(); // 返回可序列化的初始状态对象 return { user: { data: user }, settings: appSettings, }; } export default function App() { // 获取Loader返回的初始状态(服务端、客户端均可获取) const initialState = useLoaderData<typeof loader>(); // 基于初始状态创建Store const store = makeStore(initialState); return ( <html lang="zh-CN"> <body> <Provider store={store}> <Outlet /> </Provider> </body> </html> ); }
3. 页面级数据同步到Redux
针对页面专属数据,在对应页面的Loader中获取数据,再在组件内同步到Redux Store:
// app/routes/posts.tsx import { useLoaderData, useDispatch } from '@remix-run/react'; import { setPosts } from '../features/posts/postsSlice'; import { RootState } from '../store'; import { useSelector } from 'react-redux'; // 服务端执行:获取页面专属数据 export async function loader() { const posts = await fetchPostsList(); return { posts }; } export default function PostsPage() { const { posts } = useLoaderData<typeof loader>(); const dispatch = useDispatch(); // 从Redux获取数据,确保渲染基于Store状态 const reduxPosts = useSelector((state: RootState) => state.posts.list); // 同步数据到Redux:服务端渲染时执行,客户端hydrate时也会执行 // 通过比对避免重复dispatch,确保两端状态一致 if (JSON.stringify(reduxPosts) !== JSON.stringify(posts)) { dispatch(setPosts(posts)); } return ( <div className="posts-list"> {reduxPosts.map(post => ( <article key={post.id}> <h2>{post.title}</h2> <p>{post.content}</p> </article> ))} </div> ); }
关键注意事项
- 序列化要求:Loader返回的初始状态必须是可序列化的(不能包含函数、Symbol、循环引用等),否则Remix无法在服务端与客户端间传递数据。
- Hydration一致性:客户端创建Store时必须使用与服务端完全相同的初始状态,否则会出现Hydration错误,通过
useLoaderData可确保两端数据一致。 - 服务端Store隔离:每个服务端请求都会创建独立的Store实例,无需担心状态污染问题。
内容的提问来源于stack exchange,提问作者Meow
相关产品推荐
相关产品推荐

