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

如何在NextJS生产环境中正常使用Relay并实现SSR适配?

Next.js 接入 Relay 问题解决方案

构建阶段意外发起GraphQL请求修复

  • 强制指定页面渲染模式,阻断静态预生成逻辑
    • 若使用App Router,在对应动态页面顶部添加配置:
      export const dynamic = 'force-dynamic'
      该配置会告知Next.js不要在build阶段预渲染该页面,所有逻辑均在请求到达时执行。
    • 若使用Pages Router,确保页面仅导出getServerSideProps、不要导出getStaticProps/getStaticPaths,只要页面存在getServerSideProps导出,Next.js会自动跳过该页面的构建阶段静态生成。
  • 排查全局初始化逻辑:不要在_app.tsx/_document.tsx/根布局组件的顶层同步执行Relay查询,这类逻辑会在构建阶段执行,触发对GraphQL服务的请求。

Relay SSR 适配落地实践

Hook无法在getServerSideProps中使用、环境实例无法复用的问题,本质是Relay SSR的常见认知误区:Relay的SSR逻辑不需要跨服务端数据预取逻辑和React组件上下文共享同一个环境实例——服务端每个请求本身就是独立上下文,全局单例的Relay环境反而会造成多用户间缓存数据串流的严重问题。
正确实现思路是:每次服务端请求创建独立的Relay环境完成数据预取,将预取后的缓存数据序列化传到前端,前端初始化环境时注入这部分缓存完成注水,前后端状态完全对齐。

具体实现步骤

  1. 封装Relay环境工厂函数,禁止导出全局单例
// src/relay/createRelayEnvironment.js
import { Environment, Network, RecordSource, Store } from 'relay-runtime';

/**
 * 创建Relay环境实例
 * @param {Object} requestHeaders - 请求头,用于服务端透传cookie、鉴权信息
 */
export function createRelayEnvironment(requestHeaders = {}) {
  const network = Network.create(async (operation, variables) => {
    const response = await fetch('https://localhost:3000/api/graphql', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        ...requestHeaders,
      },
      body: JSON.stringify({
        query: operation.text,
        variables,
      }),
    });
    return response.json();
  });

  const recordSource = new RecordSource();
  const store = new Store(recordSource);
  return new Environment({ network, store });
}
  1. Pages Router 下getServerSideProps预取逻辑实现
// pages/[your-dynamic-route].js
import { loadQuery } from 'react-relay';
import { createRelayEnvironment } from '@/relay/createRelayEnvironment';
import PageQuery from '@/__generated__/PageQuery.graphql';

export async function getServerSideProps(context) {
  // 基于当前请求头创建独立的Relay环境
  const relayEnv = createRelayEnvironment(context.req.headers);
  // 预取页面依赖的查询,变量从路由参数、请求上下文里取即可
  await loadQuery(relayEnv, PageQuery, {
    pageId: context.params.pageId,
  });
  // 提取环境中已缓存的查询结果,序列化后作为props传给前端
  const initialRecords = relayEnv.getStore().getSource().toJSON();

  return {
    props: {
      initialRecords,
      queryVariables: {
        pageId: context.params.pageId,
      },
    },
  };
}
  1. 前端环境注水与Provider注入
    修改自定义_app组件,用服务端传回的缓存数据初始化前端Relay环境,避免客户端重复发起请求:
// pages/_app.js
import { RelayEnvironmentProvider } from 'react-relay';
import { createRelayEnvironment } from '@/relay/createRelayEnvironment';
import { RecordSource } from 'relay-runtime';
import { useMemo } from 'react';

export default function App({ Component, pageProps }) {
  const relayEnvironment = useMemo(() => {
    const env = createRelayEnvironment();
    // 注入服务端预取的缓存数据
    if (pageProps.initialRecords) {
      env.getStore().publish(new RecordSource(pageProps.initialRecords));
    }
    return env;
  }, [pageProps.initialRecords]);

  return (
    <RelayEnvironmentProvider environment={relayEnvironment}>
      <Component {...pageProps} />
    </RelayEnvironmentProvider>
  );
}
  1. 页面组件消费数据
    直接在页面组件中调用usePreloadedQuery即可,此时数据已经存在于Relay缓存中,不会发起重复请求:
// pages/[your-dynamic-route].js 续
import { usePreloadedQuery } from 'react-relay';

export default function DynamicPage({ queryVariables }) {
  const pageData = usePreloadedQuery(PageQuery, queryVariables);
  // 正常使用pageData渲染即可
  return <div>{/* 页面业务逻辑 */}</div>;
}

踩坑提醒

  • 客户端路由跳转预加载场景,直接在组件内调用useRelayEnvironment()拿到Provider注入的环境实例,再调用loadQuery做预取即可,这部分是纯客户端逻辑,不影响SSR流程。
  • 若使用App Router,可直接在Server Component中创建Relay环境完成数据查询,将序列化后的缓存和数据传给Client Component内的RelayProvider即可,不需要依赖getServerSideProps。
  • 服务端环境必须按请求创建,不能复用单例,否则会出现用户间数据泄露的问题。

内容的提问来源于stack exchange,提问作者Jessica Wentling

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 10:36:24