星火艺坊首页 SSR 性能优化实战
in 星火艺坊 with 0 comment

星火艺坊首页 SSR 性能优化实战

in 星火艺坊 with 0 comment

本文记录了我所做一次系统性的首页性能优化:在保留 SSR 的前提下,把大面积的客户端组件拆解为“Server 外壳 + 小型 Client 子组件”,并优化媒体加载策略(首屏只解码必要帧、按需预热、避免重复请求)。

背景与问题

优化一:恢复 SSR 能力,缩小客户端范围

问题:公共布局 顶部 use client 导致整棵子树都在客户端渲染。

对比:

// 之前(反例):整个布局客户端化,丢失 SSR 优势
"use client";
import { Navbar } from "@/components/(public)/Navbar";
// ...

export default function PublicLayout({ children }: { children: React.ReactNode }) {
  // useIsHomePage() 等客户端逻辑
  return (
    <div>
      <Navbar />
      <main>{children}</main>
    </div>
  );
}
// 现在(推荐):布局是 Server 组件,仅把 Navbar 保持为小 Client 岛
import { Navbar } from "@/components/(public)/Navbar";
// ...

export default function PublicLayout({ children }: { children: React.ReactNode }) {
  return (
    <div className="relative">
      <Navbar />            {/* 小型客户端组件 */}
      <main className="sm:min-w-[1005px]">{children}</main>
      {/* Footer / FooterBar 等保持 SSR */}
    </div>
  );
}

优化二:把“展示型区块”改为 Server 外壳 + 小 Client 子组件

问题:首页楼层组件大量使用 use client,导致首屏 JS 与 hydration 暴涨。

以 新闻中心 楼层为例:

// 之前:整个区块是 Client 组件(反例)
"use client";
import { Table } from "@heroui/table";
export const NewsCenter = () => (
  <Table /* ... */>...</Table>
);
// 现在:Server 外壳 + Client 子组件
import { FloorContainer } from "./components/FloorContainer";
import { NewsTableClient } from "@/app/(public)/overview/components/NewsTableClient";

export const NewsCenter = () => {
  return (
    <FloorContainer /* SSR 渲染标题/说明等纯展示区域 */>
      <NewsTableClient columns={columns} mobileColumns={mobileColumns} rows={rows} />
    </FloorContainer>
  );
}
// NewsTableClient.tsx(小型 Client 子组件)
"use client";
import { Table } from "@heroui/table";
import { useRouter } from "next/navigation";

export function NewsTableClient({ columns, mobileColumns, rows }) {
  const router = useRouter();
  const handleRowAction = (key) => {
    const item = rows.find((r) => r.key === String(key));
    if (!item?.link) return;
    item.link.startsWith("http") ? window.open(item.link, "_blank") : router.push(item.link);
  };
  return (
    <>
      {/* PC / H5 表格... */}
      <Table isHeaderSticky onRowAction={handleRowAction}>...</Table>
    </>
  );
}

文件中心 楼层也采取同样模式:Server 外壳 + DocumentAccordionClient 仅承载手风琴的“选中态”。

架构示意图:

image-20250813152817171

优化三:将 CSS 背景图替换为 next/image(懒加载 + 首次 hover 才加载并复用)

问题:部分区块(如 故事库 楼层桌面端)使用 CSS 背景图,首屏就会并发加载所有卡片图片,造成带宽争抢。hover 切换图也会每次重新请求。

onMouseEnter={(e) => {
  setHoveredIndex(index);
  setVisitedHover((prev) => {
    const next = new Set(prev);
    next.add(index);
    return next;
  });
  // 预热 hover 图
  try {
    if (cardData.hoverImage) {
      const img = new window.Image();
      img.src = cardData.hoverImage;
    }
  } catch {}
  e.currentTarget.style.left = "0%";
}}

Hover 加载状态机(简化版):

image-20250813153815317

效果与经验

小提示:

结语

这次优化的核心理念是“让 SSR 真正发挥作用 + 让客户端 JS 只做该做的事”。Server 外壳直出结构与文案,交互则拆解为小而精的“客户端岛屿”;媒体层面严格按需加载、按需预热、一次加载多次复用。这样既提升了 LCP,又避免了重复拉流与资源浪费,同时保持了现有样式和用户体验。

留言: