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

如何让Next.js RSC兼具PPR/流式SSR加载与SWR客户端数据更新特性?

方案:在Next.js RSC中结合服务端渲染优势与SWR客户端特性

核心思路是服务端用RSC+PPR完成初始数据渲染与流式交付,客户端通过SWR接管后续数据生命周期管理,既享受RSC的服务端优势,又复用SWR成熟的自动更新、重验证、乐观更新等特性,无需重复造轮子。

具体实现步骤

1. 服务端组件(RSC)处理初始数据加载与PPR交付

在页面的服务端组件中,通过带标签的fetch获取初始数据,配置PPR所需的动态渲染规则,将初始数据传递给客户端组件。

// app/experiment/[id]/page.js
import ExperimentClient from './ExperimentClient';

// 启用PPR/流式动态渲染
export const dynamic = 'force-dynamic';

async function fetchExperimentMetadata(id) {
  const res = await fetch(`https://your-api.com/experiments/${id}`, {
    next: {
      tags: [`experiment-meta-${id}`], // 标记用于服务端触发重验证
      revalidate: 0 // 确保每次请求都获取最新数据(适配PPR)
    }
  });
  if (!res.ok) throw new Error('Failed to fetch experiment metadata');
  return res.json();
}

export default async function ExperimentDetailPage({ params }) {
  const initialMetadata = await fetchExperimentMetadata(params.id);
  // 将初始数据传递给客户端组件
  return <ExperimentClient initialData={initialMetadata} experimentId={params.id} />;
}

2. 客户端组件封装SWR逻辑,接管数据更新

在客户端组件中,以服务端传递的初始数据作为SWR的initialData,配置SWR的自动更新规则(轮询、聚焦重验证等),同时实现乐观更新等特性。

// app/experiment/[id]/ExperimentClient.jsx
'use client';

import useSWR from 'swr';

// 通用请求函数
const fetcher = (url) => fetch(url).then(res => res.json());

export default function ExperimentClient({ initialData, experimentId }) {
  const { data, error, mutate } = useSWR(
    `https://your-api.com/experiments/${experimentId}`,
    fetcher,
    {
      initialData, // 复用服务端渲染的初始数据,避免客户端重复请求
      refreshInterval: 60000, // 每分钟轮询一次(匹配元数据更新频率)
      revalidateOnFocus: true, // 页面聚焦时自动重验证
      revalidateOnReconnect: true, // 网络重连时自动重验证
      shouldRetryOnError: false // 根据需求调整错误重试策略
    }
  );

  // 乐观更新示例:修改实验状态
  const updateExperimentStatus = async (newStatus) => {
    // 先本地更新UI,提升用户体验
    mutate({ ...data, status: newStatus }, false);
    try {
      await fetch(`https://your-api.com/experiments/${experimentId}`, {
        method: 'PATCH',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ status: newStatus })
      });
      // 成功后触发SWR重验证,确保数据一致性
      mutate();
    } catch (err) {
      // 失败回滚到之前的数据
      mutate(initialData, false);
      alert('状态更新失败,请稍后重试');
    }
  };

  if (error) return <div>加载失败:{error.message}</div>;
  // 服务端已渲染初始数据,这里的加载状态仅针对客户端后续请求
  if (!data) return <div>更新中...</div>;

  return (
    <div className="experiment-meta">
      <h2>实验 {data.id}: {data.name}</h2>
      <p>当前状态:<span className="status-tag">{data.status}</span></p>
      <button onClick={() => updateExperimentStatus('running')}>启动实验</button>
      <button onClick={() => updateExperimentStatus('paused')}>暂停实验</button>
    </div>
  );
}

3. 日志页面的特殊处理(每秒更新)

对于/experiment/[id]/log这类高频更新的页面,可调整SWR的轮询间隔至1秒,或结合实时数据流(如WebSocket、Firebase)触发SWR的mutate更新数据:

// app/experiment/[id]/log/LogClient.jsx
'use client';

import useSWR from 'swr';
import { useEffect } from 'react';

const fetcher = (url) => fetch(url).then(res => res.json());

export default function LogClient({ initialLogs, experimentId }) {
  const { data: logs, mutate } = useSWR(
    `https://your-api.com/experiments/${experimentId}/logs`,
    fetcher,
    {
      initialData: initialLogs,
      refreshInterval: 1000 // 每秒轮询一次
    }
  );

  // 结合Firebase流式更新(可选)
  useEffect(() => {
    const unsubscribe = firebase.firestore()
      .collection(`experiments/${experimentId}/logs`)
      .onSnapshot(snapshot => {
        const newLogs = snapshot.docs.map(doc => doc.data());
        // 触发SWR更新本地数据
        mutate(newLogs, false);
      });

    return () => unsubscribe();
  }, [experimentId, mutate]);

  if (!logs) return <div>加载日志中...</div>;

  return (
    <div className="log-stream">
      {logs.map(log => (
        <div key={log.timestamp} className="log-entry">
          <span>{new Date(log.timestamp).toLocaleTimeString()}</span>
          <span>{log.message}</span>
        </div>
      ))}
    </div>
  );
}

方案优势

  • 服务端优势保留:初始数据通过RSC+PPR流式渲染,首屏加载快、SEO友好,无需客户端hydrate后再请求数据。
  • SWR特性复用:直接使用SWR的轮询、聚焦重验证、乐观更新、错误重试等成熟特性,无需自行实现复杂的客户端数据逻辑。
  • 服务端重验证兼容:若需后台触发全局数据更新,可通过revalidateTag(experiment-meta-${id})更新服务端缓存,用户下次访问时会获取最新初始数据,不影响客户端SWR的实时管理。

内容的提问来源于stack exchange,提问作者Sunghyun Cho

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 00:07:37