> For the complete documentation index, see [llms.txt](https://onenesss-organization.gitbook.io/jj-xiaoce/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://onenesss-organization.gitbook.io/jj-xiaoce/next.js-kai-fa-zhi-nan/81.-mian-shi-pian-chang-jian-mian-shi-ti-ji-jie-xi-2.md).

# 81. 面试篇 ❘  常见面试题及解析（2）

### 1. 服务端组件和客户端组件

#### 1.1. 常见问题

服务端组件和客户端组件的区别有哪些？什么时候用服务端组件，什么时候用客户端组件？

#### 1.2. 解题思路

Next.js 的基础问题，正常回答即可

#### 1.3. 八股答案

在 Next.js 中，组件默认就是服务端组件。如果在文件顶部添加一个 "use client" 声明，则是声明为客户端组件。

使用服务端组件的好处：

1. 数据获取：通常服务端环境（网络、性能等）更好，离数据源更近，在服务端获取数据会更快。通过减少数据加载时间以及客户端发出的请求数量来提高性能
2. 安全：在服务端保留敏感数据和逻辑，不用担心暴露给客户端
3. 缓存：服务端渲染的结果可以在后续的请求中复用，提高性能
4. bundle 大小：服务端组件的代码不会打包到 bundle 中，减少了 bundle 包的大小
5. 初始页面加载和 FCP：服务端渲染生成 HTML，快速展示 UI
6. Streaming：服务端组件可以将渲染工作拆分为 chunks，并在准备就绪时将它们流式传输到客户端。用户可以更早看到页面的部分内容，而不必等待整个页面渲染完毕

使用客户端组件的好处：

1. 交互性：客户端组件可以使用 state、effects 和事件监听器，意味着用户可以与之交互
2. 浏览器 API：客户端组件可以使用浏览器 API 如地理位置、localStorage 等

组件如何选择：

| 如果你需要……                                           | 服务端组件 | 客户端组件 |
| ------------------------------------------------- | ----- | ----- |
| 获取数据                                              | ✅     | ❌     |
| 访问后端资源（直接）                                        | ✅     | ❌     |
| 在服务端上保留敏感信息（访问令牌、API 密钥等）                         | ✅     | ❌     |
| 在服务端使用依赖包，从而减少客户端 JavaScript 大小                   | ✅     | ❌     |
| 添加交互和事件侦听器（onClick(), onChange() 等）               | ❌     | ✅     |
| 使用状态和生命周期（useState(), useReducer(), useEffect()等） | ❌     | ✅     |
| 使用仅限浏览器的 API                                      | ❌     | ✅     |
| 使用依赖于状态、效果或仅限浏览器的 API 的自定义 hook                   | ❌     | ✅     |
| 使用 React 类组件                                      | ❌     | ✅     |

#### 1.4. 回答参考

简单的来说，如果需要添加事件、使用状态和生命周期以及使用浏览器的 API，都需要声明为客户端组件，除此之外，都应该尽可能使用服务端组件。

为了尽可能多的使用服务端组件，常用的技巧有两个，一个是客户端组件下移，尽可能减少客户端组件的范围，降低客户端组件在组件树的位置。另一个是将服务端组件作为 props 传给客户端组件。

服务端组件和客户端组件的最大区别，表明上看是各种用法上的差别，但我理解根本上还是渲染原理上的差别。像服务端组件在服务端进行渲染，渲染为 HTML 传给客户端，流程就结束了。但是客户端组件它会先在服务端进行一次预渲染，传给客户端后还要进行一次水合，添加事件处理程序，最后根据客户端事件进行更新。所以客户端组件我觉得可以简单粗暴的理解为“SSR + 水合 + CSR”。

### 2. 性能优化

#### 2.1. 常见问题

如何优化 Next.js 应用程序的性能？Next.js 有哪些常用的性能优化手段？

#### 2.2. 解题思路

考察对 Next.js 常用性能优化方法的理解，性能优化更多是开发者完成工作之外做的事情，考察这些事情是为了发现面试者的做项目的能力和态度，所以在回答问题本身之外，可以引申到自己在实际项目中做的一些性能优化相关的工作经历。

#### 2.3. 八股答案

Next.js 有哪些常用的性能优化手段？

1. 渲染策略

Next.js 存在三种不同的服务端渲染策略，1. 静态渲染 2. 动态渲染 3. Streaming，选择合适的渲染策略可以大大提高加载速度

2. 缓存策略

Next.js 中有四种缓存策略，基于 React 的函数记忆化、跨请求和部署的数据缓存、完整路由缓存和客户端路由缓存。默认情况下，Next.js 会尽可能多的使用缓存以提高性能和降低成本。

3. 减少 JavaScript 大小

尽可能多的使用服务端组件，将客户端组件树的位置尽可能下移，减少客户端组件 bundle 的大小。Next.js 也提供了 next/dynamic 实现组件懒加载，进一步拆分代码。

4. 优先使用内置组件

Next.js 内置的 Image 组件内置了懒加载、图片优化等功能，内置的字体组件可以在构建的时候将谷歌字体下载到本地，避免重复请求。内置的 Link 组件默认带预加载功能等。由于自带多种优化，在使用 Next.js 的时候，优先使用内置组件。

5. 使用性能测量工具

Next.js 提供了 @next/bundle-analyzer 插件用于分析管理 JavaScript 模块大小。可以根据分析图，删除不必要的依赖项或者拆分代码。同时 Next.js 内置了性能测量和上报。可以使用 useReportWebVitals hook 进行上报或者依托于 Verecl 自动进行收集。

#### 2.4. 回答参考

Next.js 本身就内置了各种性能优化方法。比如它提供了三种服务端渲染策略，静态渲染、动态渲染和 Streaming，有四种缓存策略，基于 React 的函数记忆化、跨请求和部署的数据缓存、完整路由缓存和客户端路由缓存。默认情况下，Next.js 会自动判断渲染策略，并尽可能多的使用缓存以提高性能和降低成本。

在开发的时候，尽可能使用服务端组件，对于非首屏的代码可以使用 next/dynamic 实现懒加载，进一步降低客户端 bundle 的大小。Next.js 内置的各种组件都自带了各种优化，比如 Image 组件内置懒加载和图片优化，Link 组件默认预加载等。尽可能使用内置组件。

此外，Next.js 还内置了性能测量和优化工具，比如 useReportWebVitals hook 可以上报性能数据，也有 bundle-analyzer 插件用于分析 bundle 依赖等。

其实性能优化的方法本身是有很多的，哪怕不从 Next.js 本身的层面，从网络层面、服务端层面也有一些优化方法。但我个人觉得，做性能优化最重要的是不要空想，不能自嗨，一定要数据先行。**（引申到自己的性能优化体验和经历）**

性能优化只是改善前端项目的方式之一，最终你要改善的是用户体验和开发体验，为用户、公司和开发者带来价值才行。所以一要准确的发现问题，二是要选择尽可能 ROI 高的优化方式，三是要用数据和案例证明它的价值。

所以做性能优化，要先把性能数据的测量和上报做好。像浏览器有 Lighthouse 可以直接测量性能。Vercel 本身提供了 speed-insights 可以快捷接入，看到页面的核心 Web 指标数据，但是免费版有限制，我后面试着接入了 Prometheus 和 Grafana，通过上报 Web 指标建立可视化后台，不过遇到了一些问题卡住了。**（留个悬念，面试官对这种“失败”的事情可好奇了，做好对应的故事准备即可）**

而在具体的数据指标中，其实主要看的就是 6 个指标，就是 Chrome 总结的那 6 个，核心是三个，衡量加载速度的 LCP，衡量互动的 INP，衡量视觉稳定性的 CLS，除此之外还有第一字节时间 (Time to First Byte，TTFB)、首次内容绘制 (First Contentful Paint，FCP)、首次输入延迟 (First Input Delay ，FID)，性能上的优化就是将这些值都优化到合理的范围内，监控这些数据也是对应用的用户体验进行客观衡量。

注：最后应该再上一个故事，比如我接手过一个前台 APP 应用（把锅甩给同事），用户反馈页面卡顿（临危受命），但是相关的各种指标监控都没有（从零到一），我先用工具测试了一下页面性能，然后进行了一些常见的优化工作，加载时间从之前的 12s 提升到了 4s（先解决问题），然后我开始着手用建立了性能监控后台（系统解决问题），发现页面的 XXX 指标时间比较长，经过排查是 XXX 惹的祸，最终进行了优化，将加载时间提升到了 2s，然后就这些事情总结了技术方案，做了团队分享，拿到了领导同事的表彰（升华一下）……balabala

### 3. 使用中的问题

#### 3.1. 常见问题

Next.js 服务端和客户端渲染不一致导致的水合报错该怎么解决？

#### 3.2. 解题思路

这是使用 Next.js 的常见问题，通过考察这类常见问题的解决方法，判断面试者是否对日常开发中遇到的问题进行过系统总结和思考。

#### 3.3. 八股答案

使用 Next.js 的时候经常会遇到这类报错：

```
Error: Hydration failed because the initial UI does not match what was rendered on the server.
```

其实这个报错并不来自于 Next.js，而是来自于 react-dom。比如我们在[《源码篇 | 手写 React SSR》](https://juejin.cn/book/7307859898316881957/section/7309116396511133705)中就使用 React 的客户端 API hyrateRoot 进行水合。

之所以出现这个报错，是因为服务端预渲染的 React 树和浏览器首次渲染的 React 树不一致，换句话说，服务端渲染的 内容应该和水合时传入的内容一致，否则水合的时候无法正确复用 DOM，导致水合报错。

导致水合错误的原因有很多：

1. 渲染的时候，使用了诸如 typeof window !== 'undefined' 这样的判断

```javascript
export default function Page() {
    if (typeof window !== 'undefined') return <div>Hello Browser</div>
    return (
        <div>
            Hello Node!
        </div>
    );
}
```

比如这段代码，在服务端渲染的时候，因为在 Node 环境，渲染结果为 `<div>Hello Node!</div>`，在浏览器环境，渲染结果为 `<div>Hello Browser</div>`，前后内容不一致，就会导致水合错误

2. 渲染的时候，使用了仅限浏览器的 API，比如 window 或 localStorage
3. 渲染的时候，使用了时间相关的 API，比如 Date()
4. 浏览器扩展修改了 HTML

常用的解决方法有：

1. 使用 useEffect 仅在客户端运行

```javascript
import { useState, useEffect } from 'react'
 
export default function App() {
  const [isClient, setIsClient] = useState(false)
 
  useEffect(() => {
    setIsClient(true)
  }, [])
 
  return <h1>{isClient ? 'This is never prerendered' : 'Prerendered'}</h1>
}
```

2. 禁用特定组件的 SSR 渲染

```javascript
import dynamic from 'next/dynamic'
 
const NoSSR = dynamic(() => import('../components/no-ssr'), { ssr: false })
 
export default function Page() {
  return (
    <div>
      <NoSSR />
    </div>
  )
}
```

3. 使用 suppressHydrationWarning 消除警告

如果实在无法避免，可以添加 suppressHydrationWarning 属性消除警告

```javascript
<time datetime="2016-10-25" suppressHydrationWarning />
```

#### 3.4. 回答参考

这个问题很常见，本质上是服务端预渲染的 React 树和水合时的 React 树不一致导致，导致这个错误的原因有很多，我专门研究过还写过文章分享，大致有几种原因：

第一种是使用了 window、document 等客户端 API 判断，使用了浏览器 API，如 localstorage 等

第二种是使用了时间这类 API

我遇到的就这两种，但可能原因还有很多，据我所知，还有标签嵌套错误，比如 button 嵌套 button，浏览器插件导致渲染不一致，错误配置 CSS-In-JS 导致等等

解决方案有几种：

第一种是使用 useEffect，将在客户端运行的内容放在 useEffect 中执行

第二种是禁用组件的 SSR 渲染，据我所知，目前 Next.js 只提供了 dynamic 方法可以禁用组件的预渲染

第三种是添加一个属性，具体我忘了，这是在无法避免水合错误的时候，添加这个属性，消除水合警告

（其实我记得，但这么小众的属性，我如果记得那么牢，面试官可能怀疑我背过答案了……）

### 总结

我的答案也不一定对，也不一定符合你对“面试答案”的期望，欢迎在评论区补充自己的回答！
