本篇以知识点讲解为主,分享出来也希望能帮助到有需要的朋友。针对面试,可以直接跳到文末,一起来讨论学习呀。Trae
写作思路:先提下 SSR\SSG\ISR,再指出lighthouse 性能测试工具,接下来是最近看到的Nextjs 常见面题个人解答。
小白先可以转# Nextjs 15 踩坑入门系列专栏
一、渲染策略对性能的影响
不同的渲染方式,会直接影响用户首次看到页面内容的速度、SEO 友好度以及服务器压力。我们主要关注三种模式:
1. 服务端渲染(SSR)
原理:
- 服务器在收到请求后,先运行应用逻辑,生成完整的 HTML 页面。
- 浏览器拿到 HTML 后立刻渲染,给用户快速可视内容。
- 随后客户端执行 JavaScript,所谓“水合”(Hydration),激活页面上的交互。
Next.js 示例:
// pages/ssr-example.js
export async function getServerSideProps() {
const res = await fetch('https://api.example.com/data');
const data = await res.json();
return { props: { data } };
}
function Page({ data }) {
return (
<ul>
{data.map(item => <li key={item.id}>{item.name}</li>)}
</ul>
);
}
export default Page;
优缺点:
- 优点:每次请求都能拿到最新数据;SEO 支持好;适合需要用户个性化或认证态的场景。
- 缺点:服务器压力大;首屏渲染虽快,但整体延迟受网络和后端性能影响。
2. 静态站点生成(SSG)
原理:
- 在构建时(
npm run build
),Next.js 预先跑完所有页面,生成静态 HTML 文件。 - 用户每次访问都直接获取这些预构建文件,速度极快。
- 依旧需要 “水合” 来激活交互逻辑。
Next.js 示例:
// pages/ssg-example.js
export async function getStaticProps() {
const res = await fetch('https://api.example.com/data');
const data = await res.json();
return { props: { data }, revalidate: 60 };
}
export default function Page({ data }) {
return <h1>{data.title}</h1>;
}
优缺点:
- 优点:首屏加载最快;服务器压力最低;适合内容变化不频繁的博客、营销页等。
- 缺点:构建时间随页面数量线性增长;动态内容或最新数据获取不实时。
3. 增量静态再生(ISR)
原理:在 SSG 的基础上,给页面设置 revalidate
,让它在一定时间后自动后台更新,无需整站重构。
// pages/isr-example.js
export async function getStaticProps() {
const res = await fetch('https://api.example.com/data');
const data = await res.json();
return { props: { data }, revalidate: 60 };
}
export default function Page({ data }) {
return <p>更新时间:{new Date().toLocaleTimeString()}</p>;
}
优缺点:
- 优点:兼顾了 SSG 的性能和数据的时效性;构建速度更可控;适合大量动态页面但不需要秒级更新的场景。
- 缺点:实现更复杂,缓存失效前用户可能看到旧数据。
渲染方式选型指南
- SSR:当页面需要每次访问都拿最新数据、内容强依赖用户或地域信息时选它。
- SSG:静态内容多、更新频率低、对首页加载速度有极高要求时用它。
- ISR:页面数量大、需要定时更新但不必实时、又想保持高性能,就交给它。
二、前端性能监控的核心指标
我们可以按照lighthouse 插件,查看性能评估。
浏览器提供了 performance
API,可以用来收集绝大多数我们关心的性能指标。
2.1 获取 Timing 信息
在控制台执行:
performance.getEntriesByType('navigation')[0]
你会看到一大堆时间戳,代表了不同阶段的开始与结束:
指标 | 含义 |
---|---|
navigationStart | 浏览器准备发起文档请求的时刻 |
fetchStart | 开始请求页面资源(DNS 解析前)的时间 |
domainLookupStart/End | DNS 解析开始/结束 |
connectStart/End | TCP/HTTPS 连接开始/结束 |
requestStart | 发起 HTTP 请求的时间 |
responseStart/End | 服务器响应首字节/所有字节到达的时间 |
domInteractive | DOM 解析完成,DOM 可操作的时间 |
domComplete | 文档及所有子资源加载完毕的时间 |
loadEventStart/End | load 事件触发开始/结束的时间 |
2.2 用户关注的关键指标
-
FCP(First Contentful Paint) :首次内容绘制,网页开始渲染第一个文本、图片或 SVG 的时间。可以用:
performance.getEntriesByName('first-contentful-paint')[0].startTime
-
LCP(Largest Contentful Paint) :最大内容绘制,指页面可视区域内最大图片或文本块渲染完成的时间。推荐用
PerformanceObserver
监控。 -
TTI(Time to Interactive) :首批可交互时间,页面有大部分内容渲染完且能响应用户输入的时间。
-
其他自定义指标:
- 网络响应时间 =
responseStart - fetchStart
- DOM 准备就绪时间 =
domInteractive - fetchStart
- 总加载核心时间 =
domComplete - fetchStart
- 网络响应时间 =
四、Next.js 常见面试考察
4.1 Next.js 水合原理
Hydration(水合)是客户端将服务端渲染出的静态 HTML 与 React 组件的事件处理程序绑定在一起的过程。服务端先生成完整的 HTML 结构,浏览器渲染后再下载并执行对应的 JavaScript,遍历 DOM,将事件监听器、状态管理逻辑附着到已有节点上,使页面从静态内容变为可交互状态。
4.2 SSR 与 SSG 的区别
- SSR:SSR 是指在服务器端执行 React 逻辑,动态生成 HTML 内容,然后将生成的 HTML 发送到客户端。每次用户请求页面时,服务器都会重新渲染页面。
- SSR 对seo友好,能够实现实时更新数据,能够根据用户请求(查询参数、用户身份等)动态生成内容。缺点也很明显,每次请求服务器都要重新渲染页面,对服务器性能要求高,并且每次服务器动态生成html,导致首屏加载速度慢。
- SSG:SSG 是指在构建时一次性生成所有页面的静态 HTML 文件,后续请求直接返回这些静态文件。页面内容在构建时就已经确定,不会根据用户请求动态变化。
- SSG和SSR有很相反性:SSG 页面是预先生成的静态html,所以首屏加载速度极快,而服务器又不用每次动态渲染页面,减少了服务器的负担。缺点尼,数据更新也就不及时,每次内容更新后需要重新构建和部署,不适用于动态内容,因为没法更加用户请求动态生成内容。
- SSR 使用于新闻、电商网站,社交平台;而SSG 可扩展性好,适合大规模静态内容网站,如博客,文档网站。
4.3 App Router 与 Page Router 的区别
- App Router(基于 React Server Components):页面拆分为 Server Component 和 Client Component,支持渐进式渲染和局部刷新,减少客户端负载。
- (渐进式渲染:先加载server component内容,再按需加载client component)
- Page Router(Pages 目录):经典的请求驱动路由,使用
getServerSideProps
、getStaticProps
等 API,在请求时或构建时生成完整页面。这与早期nextjs 项目兼容性好,适用于小型项目,使用起来直观,但是不支持渐进式加载和局部更新,对大型项目页面加载过重性能受到影响。
4.4 RSC 与 SSR 的差异及水合影响
- RSC(React Server Components) :组件在服务器渲染后发送给客户端的是序列化的组件树,客户端仅在需要交互的部分进行 Hydration,而不是整个页面,都不需要为每个组件绑定事件,减少了 Hydration 负担。
- SSR:服务器直接渲染完整 HTML,客户端必须对整个页面进行 Hydration,开销更大。
4.5 Next.js 路由缓存机制
- 缓存内容:对于使用 App Router 的 RSC 路由,Next.js 会缓存已加载的组件树和数据,以便快速进行客户端导航。
- 水合变化:缓存路由后切换时,仅重 Hydrate 新页面中未缓存的部分,其它部分复用已有绑定,提升首交互性能。
现在,我来讲一个具体的 Next.js 示例:
假设你有一个电商网站,用 Next.js App Router 搭建,包含首页、产品页和博客页。用户访问首页(/
)时,会加载首页的组件和数据(比如推荐商品)。这些都被缓存起来。
当用户点击某个商品链接跳转到产品页(/products/123
)时:
缓存内容:
- Next.js 会把产品页的组件(产品展示区、评论区等)和相关数据(产品详情、价格等)缓存起来。
- 静态资源(比如产品图片)也会被缓存。
水合变化:
- 如果用户返回首页,Next.js 不会重新加载首页的所有内容。它会复用之前缓存的首页组件和数据。
- 只有新变化的部分(比如用户可能添加了新评论)才会重新水合。
再比如,如果某个产品价格变化了,缓存会失效并更新:
缓存失效与更新:
- 产品页的缓存会自动失效(因为数据变化了)。
- 下次用户访问产品页时,会获取更新后的价格信息并更新缓存。
4.6 异步组件加载处理
Next.js 利用 React.lazy 和 next/dynamic
在客户端按需加载组件:
- 初始页面只加载核心包和必要组件。可以使用React 的
React.lazy
和Suspense
。或 使用 Next.js 的next/dynamic
- 遇到动态导入时,显示 loading 占位,后台下载对应 JS。
- 下载完成后执行 Yarn(或其他包管理工具)安装依赖,再对组件进行水合(Hydrate),使其变得可交互。
4.7 Diff 算法与直接操作 DOM 的性能对比
- Diff(虚拟 DOM 比较) :比如,以react为例子。React 在内存中维护一个虚拟 DOM 树。当状态更新时,React 会生成一个新的虚拟 DOM 树,并与旧的虚拟 DOM 树进行比较(Diff)。Diff 算法会找出最小的变更集合,并将这些变更批量应用到真实 DOM 中。
- 直接操作 DOM:直接使用原生 JavaScript 操作 DOM,例如
document.getElementById
、element.innerHTML
等。每次更新都会触发重排和重绘,开销更大,而且难以管理 - 结论:使用 Diff 算法在大多数复杂 UI 更新场景下性能更优。
4.8 Next.js 首页性能优化建议(从框架角度)
- 减少 Hydration 范围:将静态内容(如纯文本、图片等)无交互区域拆分为纯 Server Component,这样客户只需要对需要交互部分进行水合。
- 开启 RSC 和 Streaming:利用流式响应让用户更快看到内容。像使用app router 组件方式,RSC 会将页面内容分段发送到客户端,客户端可以逐步渲染内容。
- 动态导入大体量组件:使用
next/dynamic
动态导入大体量组件,减少初始加载提交,这时候可以配合自定义 loading,优化用户体验。 - 使用 CDN 缓存静态资源,配置next.config.js 指定今天资源cdn地址,使用cdn 缓存机制加载资源。
如果有图片,可以使用
next/image
组件自动实现图片懒加载(lazy load)和格式化。 - 分析 Bundle:通过
next build --profile
查找大体积包并拆分。