Next.js SSR模式下如何指定特定组件在服务端渲染
Next.js SSR 首屏指定组件服务端直出方案
完全可行,核心思路是拆分首屏关键内容和非关键内容的渲染、水合边界,让above-the-fold的关键内容随SSR响应直接输出可直接绘制的HTML,不被非必要的JS加载、数据请求、水合逻辑阻塞。
常见误区:Next.js 不管是Pages Router的
getServerSideProps模式,还是App Router的服务端组件模式,默认都会把服务端执行生成的HTML字符串直接返回给浏览器,浏览器拿到HTML片段就可以直接解析绘制,不需要等客户端JS执行。你遇到的关键内容要等JS加载完才显示的问题,基本都是渲染边界拆分不合理、非关键逻辑阻塞了关键内容输出导致的,不是框架本身不支持。
1. App Router 场景(推荐)
App Router的React Server Components(RSC)能力天然适配这个需求,不需要额外引入复杂配置:
- 把所有首屏关键内容写成默认的服务端组件(不需要加
'use client'标记),这部分内容会在服务端直接生成静态HTML,随首次响应的第一块数据直接返回,浏览器拿到就能绘制,完全不依赖客户端JS。 - 非首屏、需要客户端交互的重型组件,统一标记
'use client',配合动态导入和Suspense做懒加载、延迟水合,不要让这部分组件进入首屏关键渲染路径:import { Suspense, lazy } from 'react' import HeavyListSkeleton from '@/components/skeletons/HeavyList' // 懒加载非关键客户端组件 const HeavyBelowTheFold = lazy(() => import('@/components/HeavyBelowTheFold')) export default function Page() { return ( <main> {/* 首屏关键内容,服务端直出,无JS依赖 */} <AboveTheFoldHero /> <AboveTheFoldProductInfo /> {/* 非首屏内容,懒加载+骨架占位,不阻塞首屏 */} <Suspense fallback={<HeavyListSkeleton />}> <HeavyBelowTheFold /> </Suspense> </main> ) } - 配合Next.js默认的流式渲染能力,服务端不需要等所有非关键组件、非关键接口请求完成,就可以先把首屏关键内容的HTML返回给浏览器,非关键内容渲染完成后再通过分块传输推送到前端,能大幅降低LCP等待时间。
- 注意不要在首屏关键路径的服务端组件里直接引入重型客户端组件,否则这部分组件的JS会被打包进初始加载包,拖慢水合节奏。
2. Pages Router 场景
如果暂时无法迁移到App Router,用getServerSideProps也可以实现同等效果:
- 首屏关键展示内容直接写在页面根组件的顶层,不要加任何
typeof window !== 'undefined'的客户端判断、不要放在useEffect里渲染,这部分内容会随SSR响应直接生成HTML返回。 - 所有非首屏的重型组件,统一用
next/dynamic做动态导入,配合骨架屏占位,避免阻塞首屏:import dynamic from 'next/dynamic' import ReviewListSkeleton from '@/components/skeletons/ReviewList' // 懒加载非首屏评论组件,不需要首屏直出的可以直接关ssr减少服务端压力 const ReviewList = dynamic(() => import('@/components/ReviewList'), { loading: () => <ReviewListSkeleton />, ssr: false }) export default function ProductPage({ product }) { return ( <main> {/* 首屏关键内容,SSR直出 */} <h1>{product.name}</h1> <img src={product.cover} alt={product.name} /> <div>{product.desc}</div> {/* 非首屏内容懒加载 */} <ReviewList productId={product.id} /> </main> ) } export async function getServerSideProps(ctx) { // 只在根级请求首屏关键内容的数据,非关键数据下沉到组件内部请求 const product = await fetchProduct(ctx.params.id) return { props: { product } } } - 对于需要交互的首屏组件,可以配合延迟水合方案,只给关键交互组件绑定水合逻辑,非关键组件等浏览器空闲、或者滚动到视口范围内再触发水合,减少初始JS执行的阻塞时间。
3. 关键避坑点
- 不要在SSR根级等待所有接口返回后再响应,首屏不需要的非关键数据全部下沉到组件内部请求,配合流式渲染优先输出关键内容。
- 首屏纯展示类内容不要加客户端侧的条件渲染判断,否则会出现水合前内容闪白、必须等JS执行才能显示的问题。
- 给懒加载的非关键组件提前设置和实际内容高度一致的骨架占位,避免布局偏移影响LCP评分。
内容的提问来源于stack exchange,提问作者opticon
相关产品推荐
相关产品推荐

