logo
  • 指南
  • 配置
  • 插件
  • API
  • 示例
  • 社区
  • Modern.js 2.x 文档
  • 简体中文
    • 简体中文
    • English
    • 开始
      介绍
      快速上手
      版本升级
      名词解释
      技术栈
      核心概念
      页面入口
      构建工具
      Web 服务器
      基础功能
      路由
      路由基础
      配置式路由
      数据管理
      数据获取
      数据写入
      数据缓存
      渲染
      渲染模式总览
      服务端渲染(SSR)
      服务端流式渲染(Streaming SSR)
      渲染缓存
      静态站点生成(SSG)
      React Server Components (RSC)
      渲染预处理
      样式开发
      引入 CSS
      使用 CSS Modules
      使用 CSS-in-JS
      使用 Tailwind CSS
      HTML 模板
      引用静态资源
      引用 JSON 文件
      引用 SVG 资源
      引用 Wasm 资源
      调试
      数据模拟(Mock)
      网络代理
      使用 Rsdoctor
      使用 Storybook
      测试
      Playwright
      Vitest
      Jest
      Cypress
      路径别名
      环境变量
      构建产物目录
      部署应用
      部署自托管应用
      第三方平台
      进阶功能
      使用 BFF
      基础用法
      运行时框架
      创建可扩展的 BFF 函数
      扩展 BFF Server
      扩展一体化调用 SDK
      文件上传
      跨项目调用
      优化页面性能
      代码分割
      静态资源内联
      产物体积优化
      React Compiler
      提升构建性能
      浏览器兼容性
      配置底层工具
      源码构建模式
      服务端监控
      Monitors
      日志事件
      指标事件
      国际化
      基础概念
      快速开始
      配置说明
      语言检测
      资源加载
      路由集成
      API 参考
      高级用法
      最佳实践
      自定义 Web Server
      专题详解
      模块联邦
      简介
      开始使用
      应用级别模块
      服务端渲染
      部署
      集成国际化能力
      常见问题
      依赖安装问题
      命令行问题
      构建相关问题
      热更新问题
      从 Modern.js 2.0 升级
      概述
      配置变更
      入口变更
      自定义 Web Server 变化
      Tailwind 插件变更
      其他重要变更
      📝 编辑此页面
      上一页服务端渲染(SSR)下一页渲染缓存

      #流式服务端渲染(Streaming SSR)

      流式渲染是一种先进的渲染方式,它可以在页面渲染过程中逐步返回内容,从而显著提升用户体验。

      在传统的 SSR 渲染方式中,页面的渲染是一次性完成的,需要等待所有数据加载完成后才能返回完整的 HTML。而在流式渲染中,页面的渲染是逐步完成的,可以边渲染边返回,用户能够更快地看到初始内容。

      默认模式

      Streaming SSR 是 Modern.js SSR 的默认渲染模式。当你启用 SSR 时,无需额外配置即可使用流式渲染。

      如果你需要切换到传统的 SSR 模式(等待所有数据加载完成后一次性返回),可以配置 server.ssr.mode 为 'string'。详细说明请参考服务端渲染(SSR)文档。

      相比传统 SSR 渲染:

      • 更快感知速度:流式渲染可以在渲染过程中逐步显示内容,能够以最快的速度显示业务首页
      • 更好的用户体验:通过流式渲染,用户可以更快地看到页面上的内容,而不需要等待整个页面都渲染完成后才能交互
      • 更好的性能控制:流式渲染可以让开发者更好地控制页面加载的优先级和顺序,从而更好地优化性能和用户体验
      • 更好的适应性:流式渲染可以更好地适应不同网络速度和设备性能,使得页面在各种环境下都能有更好的表现

      #开启流式渲染

      Modern.js 支持了 React 18+ 的流式渲染。当你启用 SSR 时,流式渲染默认开启:

      modern.config.ts
      import { defineConfig } from '@modern-js/app-tools';
      
      export default defineConfig({
        server: {
          ssr: true, // 默认启用流式渲染
        },
      });

      如果你需要显式指定流式渲染模式,可以配置:

      modern.config.ts
      import { defineConfig } from '@modern-js/app-tools';
      
      export default defineConfig({
        server: {
          ssr: {
            mode: 'stream', // 显式指定流式渲染模式
          },
        },
      });

      Modern.js 的流式渲染基于 React Router 实现,主要涉及 API 有:

      • Await:用于渲染 Data Loader 返回的异步数据。
      • useAsyncValue:用于从最近的父级 Await 组件中获取数据。

      #获取数据

      user/[id]/page.data.ts
      import { defer, type LoaderFunctionArgs } from '@modern-js/runtime/router';
      
      interface User {
        name: string;
        age: number;
      }
      
      export interface Data {
        data: User;
      }
      
      export const loader = ({ params }: LoaderFunctionArgs) => {
        const userId = params.id;
      
        const user = new Promise<User>(resolve => {
          setTimeout(() => {
            resolve({
              name: `user-${userId}`,
              age: 18,
            });
          }, 200);
        });
      
        return defer({ data: user });
      };

      user 是一个 Promise 类型的对象,表示需要异步获取的数据,通过 defer 处理需要异步获取的 user。注意,defer 必须接收一个对象类型的参数,不能直接传递 Promise 对象。

      另外,defer 还可以同时接收异步数据和同步数据。在下述例子中,我们等待部分耗时较短的请求,在响应后通过对象数据返回,而耗时较长时间的请求,则通过 Promise 返回:

      user/[id]/page.data.ts
      export const loader = async ({ params }: LoaderFunctionArgs) => {
        const userId = params.id;
      
        const user = new Promise<User>(resolve => {
          setTimeout(() => {
            resolve({
              name: `user-${userId}`,
              age: 18,
            });
          }, 2000);
        });
      
        const otherData = new Promise<string>(resolve => {
          setTimeout(() => {
            resolve('some sync data');
          }, 200);
        });
      
        return defer({
          data: user,
          other: await otherData,
        });
      };

      这样,应用无需等待最耗时的数据请求响应后才展示页面内容,可以优先展示部分有数据的页面内容

      #渲染数据

      通过 Await 组件,可以获取到 Data Loader 中异步返回的数据,然后进行渲染。例如:

      user/[id]/page.tsx
      import { Await, useLoaderData } from '@modern-js/runtime/router';
      import { Suspense } from 'react';
      import type { Data } from './page.data';
      
      const Page = () => {
        const data = useLoaderData() as Data;
      
        return (
          <div>
            User info:
            <Suspense fallback={<div id="loading">loading user data ...</div>}>
              <Await resolve={data.data}>
                {user => {
                  return (
                    <div id="data">
                      name: {user.name}, age: {user.age}
                    </div>
                  );
                }}
              </Await>
            </Suspense>
          </div>
        );
      };
      
      export default Page;

      Await 需要包裹在 Suspense 组件内部,Await 的 resolve 传入的是 Data Loader 异步获取的数据,当数据获取完成后, 通过 Render Props 模式,渲染获取到的数据。在数据的获取阶段,将展示 Suspense 组件 fallback 属性设置的内容。

      注意

      从 page.data.ts 文件导入类型时,需要使用 import type 语法,保证只导入类型信息,避免 Data Loader 的代码打包到前端产物中。

      在组件中,你也可以通过 useAsyncValue 获取 Data Loader 返回的异步数据。例如:

      page.tsx
      import { useAsyncValue } from '@modern-js/runtime/router';
      
      const UserInfo = () => {
        const user = useAsyncValue();
        return (
          <div>
            name: {user.name}, age: {user.age}
          </div>
        );
      };
      
      const Page = () => {
        const data = useLoaderData() as Data;
        return (
          <div>
            User info:
            <Suspense fallback={<div id="loading">loading user data ...</div>}>
              <Await resolve={data.data}>
                <UserInfo />
              </Await>
            </Suspense>
          </div>
        );
      };
      
      export default Page;

      #错误处理

      Await 组件的 errorElement 属性,可以用来处理 Data Loader 或者子组件渲染时的报错。例如,我们故意在 Data Loader 函数中抛出错误:

      page.loader.ts
      import { defer } from '@modern-js/runtime/router';
      
      export default () => {
        const data = new Promise((resolve, reject) => {
          setTimeout(() => {
            reject(new Error('error occurs'));
          }, 200);
        });
      
        return defer({ data });
      };

      然后通过 useAsyncError 获取错误,并将用于渲染错误信息的组件赋值给 Await 组件的 errorElement 属性:

      page.ts
      import { Await, useAsyncError, useLoaderData } from '@modern-js/runtime/router';
      import { Suspense } from 'react';
      
      export default function Page() {
        const data = useLoaderData();
      
        return (
          <div>
            Error page
            <Suspense fallback={<div>loading ...</div>}>
              <Await resolve={data.data} errorElement={<ErrorElement />}>
                {(data: any) => {
                  return <div>never displayed</div>;
                }}
              </Await>
            </Suspense>
          </div>
        );
      }
      
      function ErrorElement() {
        const error = useAsyncError() as Error;
        return <p>Something went wrong! {error.message}</p>;
      }

      #为爬虫等待所有内容加载完毕

      流式传输可以提高用户体验,因为当页面内容可用时,用户可以及时感知到它们。

      然而,当一个爬虫访问该页面时,它可能需要先加载所有内容,直接输出整个 HTML,而不是渐进式地加载它。

      Modern.js 使用 isbot 对请求的 user-agent,以判断请求是否来自爬虫。

      #相关文档

      • 渲染模式总览
      • 服务端渲染(SSR)
      • 渲染缓存
      • React Server Components(RSC) - 与 Streaming SSR 结合使用
      • New Suspense SSR Architecture in React 18 - React 18 架构说明