next + 微前端有没有搞头 | 从CSR到混合渲染

37 阅读8分钟

先取女王后取经,不负如来不负卿--微前端做的架构拆分又嫌弃单页应用(CSR)访问速度过慢?不行,我全都要

背景

初期架构:敏捷优先的技术选择

在项目启动阶段,为快速响应业务迭代需求,我们可能会采用客户端渲染(CSR)架构搭建核心应用,配合微前端框架qiankun实现多团队并行开发与模块解耦。该方案有效支撑了业务高速发展期的高频需求交付,但伴随系统复杂度提升,技术债逐渐显现。

优化瓶颈:CSR的天花板效应

随着用项目稳定性增长与项目优化需求升级,首屏性能(FCP>3s)、搜索引擎可见性、静态资源加载效率等指标成为关键瓶颈。深度分析发现:CSR强依赖浏览器端(即使配合cdn)JS解析的特性,导致首屏渲染链路冗长、TTFB时间不可控,且静态内容缺失严重制约SEO效果。我们测算得出,纯CSR架构的性能优化空间不足较低,需突破性技术重构。

破局之道:混合渲染架构升级

基于「渐进增强」理念,我们重构技术底座为CSR+SSR+SSG三元架构:

  • 动态业务层:保留CSR主导的交互密集型模块(ecs vpc 各子应用业务层
  • 服务端渲染层:对SEO强依赖页面(官网首页/产品介绍页)启用SSR|ISR
  • 静态生成层:高频访问且内容稳定的帮助中心文档系统采用SSG预生成

技术前瞻:渲染粒度的精细化管控

当前架构支持按路由级/组件级动态选择渲染模式,配合边缘计算节点实现动静资源智能分发。将进一步探索ISR(增量静态再生)、DSG(按需生成)等前沿方案,构建弹性渲染中台。

一、升级策略

1.1 架构升级决策

  1. Umi的定位与局限性:中后台场景的「效率优先」与SSR的「断代危机」

    1. 开发提效:Umi4+Ant Design组合仍是中后台系统的好搭档,内置路由/状态管理等工具链可节省开发周期快速实现CRUD/权限管理等高频需求
    2. SSR技术弃用:Umi4官方已停止维护SSR模块(文档明确标注「不建议生产使用」),导致服务端渲染能力被锁定在旧版本
  2. Next.js的破局优势:全栈渲染的「工业级解决方案」

    1. 渲染模式全覆盖:成熟的SSR/SSG功能、社区生态

1.2 架构迁移路径

  1. 阶段二:主应用Next化

    1. 客户端渲染生态替换

      • qiankun插件
      • 状态管理库
      • 请求管理
      • ......
    2. 微前端融合方案:

      • 在Next主应用中集成qiankun,通过自定义loadMicroApp完成子应用服务端注册
      • 客户端动态加载子应用(文件路由系统 + middleware路由劫持)
    3. 核心模块重构:

      • 首页/产品页
      • 登录鉴权模块
      • 控制台混合渲染
  2. 阶段二:文档系统SSG化

    • 使用getStaticPaths+getStaticProps预生成所有帮助文档页
    • 配合CDN边缘缓存,实现访问速度≤300ms
    • 内容更新定时触发,自动重建增量页面

二. 框架及其层次关系

定义模块内部类的结构和依赖关系

2.1 生态迁移关系

image.png

2.2 微前端架构图

image.png

2.3 Next15 中的 use server / use client

  • 'use server':标记服务器端逻辑(Server Actions 或模块),不渲染 UI。

  • 'use client':标记客户端组件,支持交互,通常用于 CSR 或混合渲染。

  • 它们与渲染方式(SSR/SSG/ISR/CSR)是正交的,作用是区分代码执行位置,而不是直接决定渲染模式。

  • 让整个页面是服务端组件,这样就比较灵活的引入客户端组件和服务端组件

2.4 Next15 中的 App Router 中 SSG、SSR、ISR 和 CSR 的使用指南

  1. 静态生成 (SSG - Static Site Generation)

    Next.js 15 的 App Router 通过组件类型(服务端/客户端)抽象了传统渲染模式,但 SSGSSRCSRISR 的底层机制仍然存在,但是通过混合渲染模式开发者只需关注“服务端逻辑”与“客户端逻辑”的划分,而非显式选择渲染模式

  2. 静态生成 (SSG - Static Site Generation)

    • 在 App Router 中,默认情况下所有路由都是静态生成的。
    • 在构建时生成页面
    • 使用 fetch 但未指定动态缓存(默认 cache: 'force-cache'
    // 默认渲染就是SSG
    export default async function Page() {
      return <div>SSG</div>;
    }
    
    // or
    
    // 这个函数在构建时被调用
    // app/products/[id]/page.js
    export async function generateStaticParams() {
      const res = await fetch('https://api/products'); // 构建时获取所有产品ID
      const products = await res.json();
      return products.map((p) => ({ id: p.id })); // 预生成 /products/1、/products/2 等静态页
    }
    
    // 使用 `generateStaticParams` 返回的 `params` 静态生成此页面的多个版本
    export default async function Page({
      params,
    }: {
      params: Promise<{ slug: string }>
    }) {
      const { slug } = await params
      // ...
    }
    
  3. 服务器端渲染 (SSR - Server-Side Rendering)

    • 要为页面使用服务端渲染,你需要导出一个名为 dynamic = 'force-dynamic' 的字段
    • 或者通过cache: 'no-store' 禁用缓存每次请求时重新生成
    export const dynamic = 'force-dynamic'; // 禁用静态生成,每次请求都渲染
    
    export default function Page({ data }) {
      // 渲染数据...
      const res = await fetch('https://api/user', { 
        cache: 'no-store',  // 或者:禁用缓存,触发每次请求时重新生成
        headers: { Cookie: request.headers.get('Cookie') } // 依赖请求头
      });
      const user = await res.json();
      return <div>用户信息:{user.name}</div>;
    }
    
  4. 客户端渲染(CSR - Client-Side Rendering)

    • 实现客户端渲染

      • 在页面中使用 React 的 useState()useEffect() 钩子
      • 使用数据获取库,如 SWR,useRequest
    • 在交互时渲染新的内容

    'use client';
    import React, { useState, useEffect } from 'react'
     
    export function Page() {
      const [data, setData] = useState(null)
     
      useEffect(() => {
        const fetchData = async () => {
          const response = await fetch('https://api.example.com/data')
          if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`)
          }
          const result = await response.json()
          setData(result)
        }
     
        fetchData().catch((e) => {
          // 根据需要处理错误
          console.error('获取数据时发生错误:', e)
        })
      }, [])
     
      return <p>{data ? `你的数据:${data}` : '加载中...'}</p>
    }
    
  1. 增量静态渲染 (ISR - Incremental Static Regeneration)

    • 无需重新构建整个网站就能更新静态内容

    • 是 SSG 和 SSR 的混合体

    • 工作原理:

      • next build 期间,生成所有已知的页面
      • 对这些页面的所有请求都被缓存并立即响应
      • 1H 过后,下一个请求仍然会显示缓存的 (陈旧的) 页面
      • 缓存失效,并在后台开始生成页面的新版本
      • 一旦成功生成,Next.js 将显示并缓存更新后的页面
      • 如果请求,Next.js 将按需生成并缓存这个页面
    // pages/products.js
    export default function Products({ products }) {
      return (
        <ul>
          {products.map((product) => (
            <li key={product.id}>{product.name}</li>
          ))}
        </ul>
      );
    }
    
    export async function generateStaticParams() {
      const res = await fetch('https://api.example.com/products');
      const products = await res.json();
    
      return {
        props: {
          products,
        },
        // 每1小时重新验证一次
        revalidate: 60 * 60 * 60,
      };
    }
    
    
    
    // or
    // 或者不用fetch 
    export const revalidate= 60 * 60 * 60export default async function Home() {
      const res = await fetch("http://localhost:3000/api/menudata?city=1",
        { next:{ revalidate:10 }
      }).then(
        (res) => res.json()
      );
     
      return (
        <div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
          {JSON.stringify(res)}
        </div>
      );
    }
    

四、应用混合渲染设计

Next.js 推荐一种混合方法,允许你根据应用程序中每个页面的需求使用服务器端渲染静态站点生成和客户端渲染的组合

  1. next 中实现 @umi/plugin-qiankun

      子应用通过qiankun接受的数据都是静态的,而qiankun本身的通信方式initGlobalStateumi/model的使用方式大相径庭。如果采用qiankun本身的通信方式就需要一定的子应用改造成本。综上,决定自己实现 @umi/plugin-qiankun 插件

    1. 从@umi/plugin-qiankun 到 qiankun:@umi/plugin-qiankun源码分析

      • 根据 name 完成子应用的注册和切换
      • 通过 stateforSlave(useModel('@@qiankunStateFromMaster')) 完成子应用状态更新
    2. 技术实现细节

           const containerRef = useRef<HTMLDivElement>(null);
           const microAppRef = useRef<MicroAppType>(null);
           
           const register = async () => {
             // 注册微应用 需要动态注入
             const qiankun = await import('qiankun');
             const configuration = {
               globalContext: window,
             };
             if (!containerRef.current) return;
             microAppRef.current = qiankun.loadMicroApp(
               {
                 name: 'tenant',
                 entry: 'http://dev4.msxfyun.test:8000/tenant',
                 container: containerRef.current!,
                 props: {},
               },
               configuration
             );
           };
           useEffect(() => {
             if (microAppRef.current) {
               microAppRef.current?.update?.({
                 count,
               });
             }
           }, [count]);
      
           useMount(register);
      
  2. 微前端通信设计

    暂时无法在唯科之家2.0文档外展示此内容

  1. 首页渲染策略

    组件渲染方式原因
    HomeLayoutSSG静态内容,无需动态数据。
    HomeHeaderSSR包含动态用户状态,需快速渲染以优化 SEO 和首屏体验。
    HomeFooterSSG静态内容,无需动态渲染。
    HomeSearchBar UISSR初始 HTML 需快速呈现,SEO 友好。
    HomeSearchBar 交互CSR实时输入和动态结果需客户端处理。
    HomeSearch ResultsISR热门查询可缓存,定期更新平衡性能和动态性。
    UserInfo UISSR依赖用户会话,服务器端渲染确保快速呈现。
    UserInfo 交互CSR模态框和更新用户信息需客户端交互。
  1. 登录页面

  • 主账户登录

    组件渲染方式原因
    LayoutSSG静态内容,无需动态数据
    LoginForm UISSR初始HTML需快速呈现
    LoginForm ClientCSR登录表单需要与客户端交互
    TypeChangeButton ClitntCSR需要同步浏览器参数
  • 其他登录方式

    组件渲染方式原因
    LayoutSSG静态内容,无需动态数据
    Header UISSR初始HTML需快速呈现
    HeaderClinetCSR切换到首页、主账户登录、注册等需要与浏览器交互
    LoginForm UISSR初始HTML需快速呈现
    LoginFormClientCSR登录表单需要与客户端交互
  1. workbench工作台页面

    组件渲染方式原因
    LayoutSSG静态内容,无需动态数据
    快速导航 UISSR数据通过调用接口呈现
    搜索输入框 ClientCSR搜索数据交互
    快捷导航列表 ClientCSR数据获取与路径跳转
    最近访问 UISSG静态内容,无需动态数据
    最近访问列表 ClientCSR客户端交互,数据获取与路径跳转
    NavItem 组件 ClientCSR数据渲染
    资源概览 UISSG静态内容,无需动态数据
    ResourceItem 组件 ClientCSR数据渲染和功能交互