next.js服务端获取数据指南
Published: 2023-09-08 15:43:27


如果想看专栏的方式可以点击进入[Next图册目实践](http://rexweb.link/categories/Next%E5%9B%BE%E5%86%8C%E7%9B%AE%E5%AE%9E%E8%B7%B5/)

Next.js 是一个 React 框架，它提供了一种简单的方法来在服务端或客户端获取数据。在这个指南中，我们将会介绍 Next.js 中的数据获取方法，包括：

- 在客户端渲染时获取数据
- 在服务端渲染时获取数据
- 静态生成

梦兽将会先说说现在软件行业中的“病态”现象，为什么需要服务端获取数据？如果不想看这部分内容可以直接跳到`在服务端渲染时获取数据`进入本次的主题。

# 客户端渲染时获取数据。
## 1. 如何实现？
作为前端开发者，你应该明白我在说什么。一般我们在浏览器器客户端获取数据一般都是用ajax技术。在React中以下代码大家应该见多了这里就不详细讲解了，本系列第一章有提及到需要有React的技术。

``` ts
import { useEffect, useState } from 'react'

function MyComponent() {
  const [data, setData] = useState(null)

  useEffect(() => {
    fetch('/api/data')
      .then(response => response.json())
      .then(data => setData(data))
  }, [])

  return (
    <div>{data ? data.name : 'Loading...'}</div>
  )
}
```
## 2. 客户端获取数据有什么问题？
相信大家在日常开发过程，除了比较大的软件企业有人力资源去做API聚合服务。一般的企业有这么多财力落实？

在如今前后分化到极端的环境里中只要你是一名`前端开发工程师`都会遇到一个业务功能让你各种调用API的情况，可能一个很简单的业务功能，明明是服务端来做的。会因为说是微服务的原因不好处理，让你直接调用！

千万不要掉入这种坑，换句话说服务端开发的同时可能也因为这种混乱的微服务关系，它的服务里面可能连基本的业务判断都没有做！别问我为什么知道，这种案例我遇多了。

### 接口暴露越多系统的稳定性和安全性越差
#### 稳定性差：
由于我们把接口都暴露出去让客户端调用，本来应该有很多服务可以走内网带宽浏览且被无疑的转化成公网带宽流量。

在这个过程中，一个很简单的业务。本来用户只需要发起10kb的数据请求。由于各种API调用这个数据请求回变成40kb。这个过程全程走公网带宽。响应时长损耗直接上升内网请求的10倍。

#### 安全性差:
由客户端进行的聚合请求框架，如果你的安全系数做得不够好。建议这个接口还是让服务端同事进行聚合（当我没说这句话。打工人宁可累死别人也不可能累死自己，环境已成大局，前端聚合怎么了嘛？）。

暴露的接口越多，你的系统调用与业务逻辑越清晰。系统漏洞系统越容易呈现，这应该不需要我多说了吧。

所以有一些企业会让node.js做api聚合或者使用node.js做一个api网关服务。比如早起的支付宝使用`egg.js`做了很多这方面的事情，还有淘宝的搜索页面由越来的`php`转为`node.js`做渲染层。

#### 不利于做SEO
如果你现在需要做SEO优化，在next.js中就必须要在服务端中获取数据才能做HTML的渲染。在客户端渲染，有些蜘蛛程序是无法获取。

# 在服务端渲染时获取数据

通过之前的章节里介绍过Next.js是一个服务端渲染的web应用，同时兼容了React的客户端渲染。如果我`useEffect`是在客户端进行执行的。我们应该如何在服务端中获取数据并渲染React的真实Html。

> 注意： 我们这里使用的**App Router**，与网上很多**Page Router**中`getServerSideProps`有所不同。在**App Router**中回清晰很多。

## 在Next.js 13 App Router服务端只能使用Fetch

在Node.js 16.8版本之后Node.js已经已经可以直接使用fetch作为请求。

在Next.js中推荐在服务端获取数据，有以下好处：
- 可以直接访问后端数据源(例如数据库或者其他内网服务)
- 通过防止敏感信息向客户端泄漏
- 在统一环境中获取数据并呈现，减少客户端和服务端的来回通信
- 使用单个往返执行多个数据获取，而不是在客户端上执行多个单独的请求。
- 减少请求瀑布

这里不以为route.ts讲解，route.ts已经在前面的章节提及过。如有不清楚可以回顾之前章节。

如果我们需要在Page里面进行数据获取我们只需要在return之前执行fetch请求参数就好了。

我们服务端渲染获取baidu的首页响应请求报文为例子。

``` ts
// app/page.tsx
export default async function Home() {
  const data = await fetch("http://www.baidu.com").then(res => res.text());
  return (
    <div dangerouslySetInnerHTML={{ __html: data }}></div>
  )
}
```
在我们的浏览器中查看内容发现，可以渲染出百度的内容。

![截屏2023-09-03 18.16.03.png](https://x.imgs.ovh/x/2023/09/03/64f45d9fe91d1.png)

但我们查看源码的时候发现我们的内容依然是可以渲染出来的

![截屏2023-09-03 18.20.19.png](https://x.imgs.ovh/x/2023/09/03/64f45df990f9f.png)

看到这里你就已经是学会了服务端渲染获取数据了！！！哈哈哈是不是很简单呢？

但是我们还是需要注意一些事项！！

# 服务端获取数据注意事项！

这部分的代码会放到[github  rex-huaban 的v3.0分支中](https://github.com/rexleimo/rex-huaban)

## 1. 禁止在客户端渲染中写以上代码！
在next.js 13中默认组件使用的是服务端组件。也就是说你要用`use hooks`,比如`useEffect`,`useCallback`等第三方库React库需要强制定义`use client`进行渲染.

``` ts
"use client"

import React, { useEffect } from 'react'

function RenderBaidu() {
    const data = React.use( fetch("https://api.vvhan.com/api/sao", { cache: 'no-cache' }).then(res => res.text()));
    useEffect(() => {
        console.log('rendering...')
    }, [])
    return (
        <div>
            来着：{data}
        </div>
    )
}

export default RenderBaidu
```

``` ts
// page.tsx
import React from 'react'
import RenderBaidu from '../components/RenderBaidu'

async function page() {
    return (
        <RenderBaidu />
    )
}

export default page
```

比如以上代码。本来`RenderBaidu`是服务直接让Server中获取数据，由于React.use的新特性赋予了新的功能。以上代码还是能渲染出来的但是可以发现客户端和服务端的消息是不同步的,同时为了避免应用出问题与死循环，

> 在2023-08-20日以后的版本以上写法将会丢失所有客户端交互的特性。

![截屏2023-09-03 19.09.38.png](https://x.imgs.ovh/x/2023/09/03/64f469886e08c.png)


## 2. 数据缓存
缓存是我们解决服务端响应速度的技巧之一，Next.js使用fetch的w3c标准默认也会给我们的请求添加了缓存，而且这个缓存如果你进行处理将永久缓存起来。

有两种解决方案

### 2.1 设置cache属性为no-cache
我们把以上代码修改为 
``` ts
import React from 'react'
import RenderBaidu from '../compoents/RenderBaidu'

async function page() {
    const data = await fetch('https://api.uomg.com/api/rand.qinghua', { cache: 'no-cache' }).then(res => res.json())
    return (
        <RenderBaidu txt={data.content} />
    )
}

export default page
```
``` ts
"use client"

import React, { useState } from 'react'

function RenderBaidu(props: { txt: string }) {
    const [count, setCount] = useState(0)
    return (
        <div onClick={() => {
            setCount(count + 1)
        }}>
            来着：{props.txt} {count}
        </div>
    )
}

export default RenderBaidu
```
这样能ssr也能csr。

## 2. Revalidation 使用next.js缓存时间进行更新
以上是在你不想使用缓存的业务场景下可以那么做。但是如果你遇到了想使用缓存是有周期性的业务就不可用了。next.js同样也提供了类似方案。

如果做后端比较多的同学会提问，为什么不让别的服务直接用Redis呢？这样不是更好或者让开发者自己使用Redis缓存不好嘛？

这里梦兽编程说说自己的看法吧，首先如果请求他人服务必定会存在网络请求带来的传输，即使是本地的Redis服务也一样存在这个问题，两个服务直接存在tcp协议的通信。而next.js的缓存存在自己应用内存中读取起来也是相当的快所以直接在这边做就好了。

### 我们要如何做呢？

我们只需设置 `{next:revalidate:(秒)}` 即可

``` ts
import React from 'react'
import RenderBaidu from '../compoents/RenderBaidu'

async function page() {
    const data = await fetch('https://api.uomg.com/api/rand.qinghua', { next: { revalidate: 60 } }).then(res => res.json())
    return (
        <RenderBaidu txt={data.content} />
    )
}

export default page
```

当然如果你想标记的方式进行检验
``` ts
import React from 'react'
import RenderBaidu from '../compoents/RenderBaidu'

async function page() {
    const data = await fetch('https://api.uomg.com/api/rand.qinghua', { next: { tags: ['a'] } }).then(res => res.json())
    return (
        <RenderBaidu txt={data.content} />
    )
}

export default page
```
在别的业务请求接口中
``` ts
import { revalidatePath } from 'next/cache'

fcuntion (){
    //...
    // 调用进行删除缓存
    revalidatePath('a')
}

```

# 结语

在这篇文章中，我们详细探讨了Next.js中获取数据的各种方法，包括在客户端和服务端渲染时获取数据，以及静态生成。我们还讨论了客户端获取数据可能遇到的问题，如系统稳定性和安全性的降低，以及不利于SEO优化。

我们也介绍了在服务端渲染时如何获取数据，并提供了一些实用的代码示例。此外，我们还强调了在客户端渲染中需要避免的一些问题，并介绍了如何使用Next.js的数据缓存功能。

希望这篇文章能帮助你更好地理解和使用Next.js中的数据获取方法。如果你有任何问题或者需要进一步的帮助，欢迎随时向我提问。

![截屏2023-08-17 23.44.00.png](https://x.imgs.ovh/x/2023/09/04/64f5f2c94bf2c.png)

感谢你的阅读，期待在下一篇文章中再次见到你！

这里是梦兽编程，本次的代码更新将会放在Github[本次项目的Github连接](https://github.com/rexleimo/rex-huaban)中的V3.0分支中

[我的B站视频号](https://space.bilibili.com/106325238?spm_id_from=333.1007.0.0)更多视频动态。

![截屏2023-08-18 00.02.24.png](https://x.imgs.ovh/x/2023/09/04/64f5f2c85195a.png)