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

React SSR场景下如何仅发起一次API请求?

解决SSR中重复API请求的方案

这确实是SSR开发里非常头疼的一个问题——两次相同的慢请求完全是在浪费资源和用户时间。好在有不少成熟的方案可以实现只发起一次请求,服务器端和客户端复用同一份数据,下面给你详细拆解:

一、通用方案:手动把服务器数据注入到HTML(无框架依赖)

如果是你自己搭建的SSR服务(比如用Express + ReactDOMServer),最直接的方式就是把服务器端获取到的数据序列化后,通过<script>标签挂载到全局对象上,客户端初始化时直接取用这份数据,跳过API请求。

服务器端代码示例:

// 1. 服务器端发起API请求获取数据
const fetchProjects = async () => {
  const res = await fetch('https://api.example.com/projects');
  return res.json();
};
const projects = await fetchProjects();

// 2. 渲染React组件并把数据注入到HTML模板
const appHtml = ReactDOMServer.renderToString(<App projects={projects} />);
const finalHtml = `
<!DOCTYPE html>
<html>
<head>
  <title>我的项目列表</title>
</head>
<body>
  <div id="root">${appHtml}</div>
  <!-- 把数据挂载到全局window对象 -->
  <script>
    window.__SSR_INITIAL_DATA__ = ${JSON.stringify(projects)};
  </script>
  <!-- 加载客户端打包文件 -->
  <script src="/client.bundle.js"></script>
</body>
</html>
`;

// 返回HTML给客户端
res.send(finalHtml);

客户端代码示例:

// 1. 优先读取全局对象中的初始数据
const initialProjects = window.__SSR_INITIAL_DATA__;

// 2. Hydrate时直接传入数据,无需再次请求
ReactDOM.hydrate(
  <App projects={initialProjects} />,
  document.getElementById('root')
);

// 3. 清理全局变量,避免污染
delete window.__SSR_INITIAL_DATA__;

这个方案的好处是完全不依赖任何框架,自定义SSR场景下非常好用,唯一需要注意的是JSON.stringify处理数据时的兼容性(比如不能包含函数、循环引用等)。

二、框架自带方案:利用SSR框架的数据预取能力

如果你用的是Next.js、Nuxt.js这类成熟的SSR框架,它们已经内置了解决这个问题的机制,不用手动操作全局对象,框架会帮你自动完成数据的传递和复用。

以Next.js为例,使用getServerSideProps方法:

// 页面组件中导出getServerSideProps,仅在服务器端执行
export async function getServerSideProps() {
  // 服务器端发起API请求
  const res = await fetch('https://api.example.com/projects');
  const projects = await res.json();

  // 返回的数据会作为props自动传递给页面组件
  return { props: { projects } };
}

// 页面组件直接接收props中的数据,客户端hydrate时不会重复请求
export default function ProjectsPage({ projects }) {
  return (
    <div className="project-list">
      {projects.map(project => (
        <div key={project.id} className="project-item">
          <h3>{project.name}</h3>
          <p>{project.description}</p>
        </div>
      ))}
    </div>
  );
}

框架会在服务器端执行getServerSideProps获取数据,渲染页面后把数据序列化注入到HTML中,客户端初始化时自动读取这些数据作为组件props,全程不需要你手动处理数据传递,非常省心。

三、状态管理方案:复用服务器端的Store状态

如果你的项目用了Redux、MobX这类状态管理库,可以在服务器端把数据填充到Store中,然后把Store的状态序列化后传递给客户端,客户端用这个初始状态初始化Store,组件从Store取数据时直接复用服务器端的结果。

服务器端代码示例(Redux):

// 1. 创建服务器端Store并填充数据
const store = configureStore();
await store.dispatch(fetchProjectsAction()); // 这个action内部发起API请求

// 2. 获取Store的初始状态并序列化
const initialState = JSON.stringify(store.getState());

// 3. 渲染组件并注入状态
const appHtml = ReactDOMServer.renderToString(
  <Provider store={store}>
    <App />
  </Provider>
);

const finalHtml = `
<!DOCTYPE html>
<html>
<head><title>我的项目列表</title></head>
<body>
  <div id="root">${appHtml}</div>
  <script>
    window.__INITIAL_REDUX_STATE__ = ${initialState};
  </script>
  <script src="/client.bundle.js"></script>
</body>
</html>
`;
res.send(finalHtml);

客户端代码示例(Redux):

// 1. 读取全局中的初始状态,创建客户端Store
const initialState = window.__INITIAL_REDUX_STATE__;
const store = configureStore(initialState);

// 2. Hydrate时使用这个Store,组件从Store取数据时直接复用服务器端数据
ReactDOM.hydrate(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);

delete window.__INITIAL_REDUX_STATE__;

这个方案适合状态复杂的大型项目,能确保服务器端和客户端的状态完全一致,避免hydrate时出现不匹配的问题。


核心思路其实都是服务器端获取数据后,把数据传递给客户端,让客户端直接复用,而不是重新发起请求。具体选哪种方案,完全取决于你的项目技术栈和复杂度:

  • 自定义SSR场景:选方案一或方案三
  • 用现成SSR框架:直接用框架自带的方案(比如Next.js的getServerSideProps、Nuxt.js的asyncData)最省事

内容的提问来源于stack exchange,提问作者Philip Fabianek

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 11:47:42