在最近的项目实践中,我遇到了一个首页性能优化的问题。首页包含一个较为复杂的 AnimatedBanner 组件,具有大量的图片和鼠标跟随动画。最初的实现方式为客户端渲染(CSR),带来了较大的首屏加载压力。本文将详细记录我如何逐步优化 AnimatedBanner,从客户端渲染 (CSR) 到静态站点生成 (SSG),并解决了在过程中遇到的各种问题。
一、初始问题分析
最开始,我的 AnimatedBanner
组件采用 use client
,通过 useEffect
直接加载动画逻辑。虽然功能完整,但带来了几个问题:
- 首屏加载 JS 量大,影响渲染速度。
- 组件树使用
use client
后,无法利用 Next.js 的静态生成优势(SSG)。
二、渐进式优化策略
我采用了一步一步的优化策略:
- 动态加载动画逻辑
- 将组件拆分为 Server 和 Client Component
- 使用 IntersectionObserver 懒加载动画
- 引入 ``** 组件懒加载**
- 使用 ``** 提升图片加载体验**
三、优化实施过程
1. 动态加载动画逻辑
原本动画逻辑通过 useEffect
直接导入,我将其修改为动态导入方式,减小初始加载体积:
tsx
useEffect(() => {
import("@/utils/bannerMouseEffect").then(({ initBannerMouseEffect }) => {
initBannerMouseEffect();
});
}, []);
2. 拆分 Server 和 Client Component
为了充分利用 Next.js 的静态生成,我将 AnimatedBanner
拆分为纯 Server Component 和 Client Component Wrapper。
- Server Component(AnimatedBanner):只负责静态 HTML 和图片渲染。
- Client Component(AnimatedBannerClient):负责加载和执行动画逻辑。
Server Component示例:
tsx
const AnimatedBanner = forwardRef(({ style }, ref) => (
<div ref={ref} className="animated-banner">
{/* 图片渲染 */}
</div>
));
AnimatedBanner.displayName = "AnimatedBanner";
Client Component示例:
tsx
"use client";
const AnimatedBannerClient = ({ style }) => {
const bannerRef = useRef(null);
useEffect(() => {
if ("IntersectionObserver" in window) {
const observer = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting) {
import("@/utils/bannerMouseEffect").then(({ initBannerMouseEffect }) => {
initBannerMouseEffect();
});
observer.disconnect();
}
});
observer.observe(bannerRef.current);
}
}, []);
return <AnimatedBanner ref={bannerRef} style={style} />;
};
3. 使用 IntersectionObserver 懒加载动画
使用 IntersectionObserver 实现只有当用户滚动到组件时才加载动画逻辑,有效提升性能。
4. 使用 next/dynamic
组件懒加载
通过 Next.js 内置的动态导入进一步优化:
tsx
const AnimatedBannerClient = dynamic(() => import("@/components/AnimatedBannerClient"), {
ssr: false,
loading: () => <div>加载中...</div>,
});
5. 提升图片加载体验:blurDataURL
为图片添加低分辨率占位图,改善加载体验:
tsx
<Image
src={item.src}
blurDataURL={item.blurDataURL}
placeholder="blur"
/>
四、遇到的问题与解决方案
优化过程中,遇到以下问题:
-
父组件使用 "use client" 后,子组件无法静态渲染问题:
解决方案:拆分组件,确保 Banner 部分为纯 Server Component,通过 Client Wrapper 加载动画。
-
forwardRef 组件 ESLint 警告问题:
添加 displayName 解决警告:
tsxAnimatedBanner.displayName = "AnimatedBanner";
-
图片未显示问题:
确保 ref 正确透传,并去除不必要的
ssr: false
配置即可。
五、最终效果及结论
通过以上优化措施,首页性能大幅提升:
- 首屏秒开:静态 HTML 直接加载,图片显示平滑。
- 动态动画延迟加载:动画逻辑按需加载,减少用户初次访问的等待时间。
- 维护性提升:组件结构清晰,后续维护更加方便。
此次优化实践证明了 Next.js 应用合理使用 Server 和 Client Components 的重要性。通过精细化控制渲染模式,完全可以实现更好的用户体验和性能。
希望本次实践能够为遇到类似问题的开发者提供一些参考和思路。