## 第 4 章：数据获取（SSR/CSR 数据方案）
本章围绕 Nuxt 4 全栈数据交互展开，先讲解三大渲染模式的执行逻辑与业务选型，再依次实现服务端接口、客户端请求、服务端请求、跨域代理、数据缓存等能力，全部搭配可运行代码与实战演练，适配小册平台的数据业务场景。

### 4.1 数据渲染模式：SSR、SSG、ISR 实战对比
Nuxt 4 支持三种主流渲染模式，直接决定数据执行时机、SEO 效果、访问性能，是内容平台开发的核心选型依据。

#### 1. SSR 服务端渲染（默认模式）
- **执行逻辑**：用户每访问一次页面，服务端实时请求接口、拼接完整 HTML 后返回浏览器，数据在服务端执行。
- **数据特点**：数据实时更新，搜索引擎可抓取完整页面内容，SEO 最优。
- **适用场景**：小册首页、详情页、评论区、实时榜单等动态内容页面。
- **启动 / 打包命令**：yarn dev / yarn build

#### 2. SSG 静态站点生成
- **执行逻辑**：执行打包命令时，一次性拉取所有页面数据，预生成纯静态 HTML 文件，后续访问不再请求服务端。
- **数据特点**：访问速度最快、服务器压力极小；缺点是数据无法自动更新，修改内容后必须重新打包。
- **适用场景**：帮助文档、平台介绍页、固定不变的栏目页。
- **打包命令**：yarn generate

#### 3. ISR 增量静态再生
- **执行逻辑**：结合 SSG + SSR 优势，打包生成静态页面并设置缓存时效；缓存过期后，后台静默重新拉取数据更新页面，无需全量重新打包。
- **数据特点**：兼顾静态访问速度与数据动态更新。
- **适用场景**：小册列表、每日推荐、资讯专栏等更新频率适中的页面。
- **ISR 代码配置**

在页面中通过 definePageMeta 设置缓存时长（单位：秒）
```js
<script setup>
// 页面缓存 300 秒，超时自动增量更新
definePageMeta({
  isr: 300
})
</script>
```

#### **实战演练**
- 1，分别执行 yarn build（SSR）、yarn generate（SSG）打包项目，对比页面数据加载效果。
- 2，给小册列表页开启 isr 配置，观察缓存过期后数据自动更新逻辑。
- 3，结合小册业务，区分哪些页面使用 SSR、SSG、ISR。

### 4.2 接口封装：server/api 目录与 Nitro 接口
Nuxt 4 内置 Nitro 服务引擎，server/api 目录遵循文件即接口规则，无需额外搭建后端项目，一套代码完成前后端开发。

#### 目录规则
- **文件路径**：server/api/xxx/xxx.ts
- **自动映射接口地址**：/api/xxx/xxx

#### 1. 基础 GET 接口（小册列表）
新建文件 server/api/book/list.ts
```ts
export default defineEventHandler(() => {
  // 模拟数据库数据
  const bookList = [
    { id: 1, title: "Nuxt4 全栈实战", author: "技术作者", views: 5200 },
    { id: 2, title: "Vue3 进阶指南", author: "前端达人", views: 3600 }
  ]
  return {
    code: 200,
    data: bookList,
    msg: "请求成功"
  }
})
```

#### 2. 接收 GET 路由参数（小册详情）
新建文件 server/api/book/detail.ts，使用 getQuery 获取地址参数
```ts
export default defineEventHandler((event) => {
  // 获取 URL 上的 query 参数
  const query = getQuery(event)
  const bookId = query.id
  return {
    code: 200,
    data: {
      id: bookId,
      title: "小册详情内容",
      author: "技术作者",
      views: 200,
      content: "这里是教程正文..."
    }
  }
})
```

#### 3. 接收 POST 请求体参数
server/api/book/add.ts
```ts
export default defineEventHandler(async (event) => {
  // 读取 POST 请求 body
  const body = await readBody(event)
  return {
    code: 200,
    data: body,
    msg: "新增小册成功"
  }
})
```

#### **实战演练**
- 1，在 server/api 目录下创建 book 目录，新建文件 server/api/book/list.ts、server/api/book/detail.ts、server/api/book/add.ts。
- 2，依次创建小册列表、详情、新增三个接口。
- 3，本地运行项目，通过地址栏 / 接口测试工具访问接口，验证数据正常返回。
- 4，测试 GET 传参、POST 传参两种参数接收方式。

### 4.3 客户端数据获取：$fetch 与请求拦截
客户端请求在浏览器端执行，适合后台管理、用户中心、点击触发类交互，使用 Nuxt 内置全局 $fetch，并可统一配置请求 / 响应拦截。

#### 1. $fetch 基础用法
用户中心
app/pages/users/index.vue
```js
<template>
  <div>
    <h3>个人中心</h3>
    <button @click="getUserInfo">获取个人信息</button>
    <div v-if="user">
      <p>用户名：{{ user.name }}</p>
      <p>账号ID：{{ user.uid }}</p>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'
const user = ref(null)

// 客户端点击后发起请求
const getUserInfo = async () => {
  const res = await $fetch('/api/user/info')
  user.value = res.data
}
</script>
```
用户查询接口
server/api/user/info.ts
```ts
export default defineEventHandler(() => {
  return {
    code: 200,
    data: {
      name: "技术作者",
      uid: "123456",
    }
  }
})
```
访问 localhost:3000/users，点击获取个人信息按钮，查看浏览器网络面板。

#### 2. 全局请求拦截（统一处理 Token、错误提示）
在 app/plugins/ 新建 fetch-intercept.ts 全局插件，实现全局拦截：
app/plugins/fetch-intercept.ts
```ts 
export default defineNuxtPlugin((nuxtApp) => {
  const customFetch = $fetch.create({
    onRequest({ options }) {
      const token = useCookie('token').value
      if (token) {
        options.headers.set('Authorization', `Bearer ${token}`)
      }
      console.log("请求加载成功")
    },
    onResponse({ response }) {
      const res = response._data
      if (res.code !== 200) {
        alert(`请求失败：${res.msg}`)
      }
    },
    onResponseError() {
      alert("网络异常，请稍后重试")
    }
  })
  console.log("fetch 插件加载成功")
  // 挂载到全局, $fetch
  nuxtApp.provide('fetch', customFetch)
})
```

#### 3. 使用自定义拦截请求
```js
const { $fetch } = useNuxtApp()
const res = await $fetch('/api/user/info')
```
app/pages/users/index.vue
```js
<template>
  <div>
    <h3>个人中心</h3>
    <button @click="getUserInfo">获取个人信息</button>
    <div v-if="user">
      <p>用户名：{{ user.name }}</p>
      <p>账号ID：{{ user.uid }}</p>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'
const { $fetch } = useNuxtApp()
const user = ref(null)

// 客户端点击后发起请求
const getUserInfo = async () => {
  const res = await $fetch('/api/user/info')
  user.value = res.data
}
</script>
```
访问 localhost:3000/users，点击获取个人信息按钮，查看浏览器网络面板。

#### **实战演练**
- 1，在用户中心页面编写 $fetch 点击请求逻辑，查看浏览器网络面板。
- 2，配置全局请求拦截，模拟无 Token、接口报错场景，验证拦截生效。
- 3，区分客户端请求与服务端请求的网络表现差异。

### 4.4 服务端数据获取：useAsyncData 与 useFetch
服务端请求在 SSR/SSG 阶段执行，数据随页面 HTML 直出，SEO 友好，是内容类页面首选方案。Nuxt 提供两个核心 API：useFetch（简易封装）、useAsyncData（底层灵活）。

#### 1. useFetch（推荐，日常高频使用）
内置加载状态、错误捕获、自动解析数据，语法简洁。
app/pages/books/index.vue 小册列表页 
```js
<template>
  <div>
    <h2>小册列表</h2>
    <!-- 加载中 -->
    <div v-if="status === 'pending'">数据加载中...</div>
    <!-- 正常渲染 -->
    <ul v-else>
      <li v-for="item in data.data" :key="item.id">
        {{ item.title }} - {{ item.author }}
      </li>
    </ul>
  </div>
</template>

<script setup>
// 服务端发起请求，无需手动 async/await 外部包裹
const { data, status} = await useFetch('/api/book/list')
</script>
```
自定义请求方式与参数
```js
const { data } = await useFetch('/api/book/add', {
  method: 'POST',
  body: { title: "新小册", author: "测试作者" }
})
```
#### 2. useAsyncData（底层 API，复杂场景使用）
支持自定义缓存 Key、数据处理逻辑，适合多数据源、复杂数据加工场景。
```js
<script setup>
const { data, pending, error } = await useAsyncData(
  'bookListKey', // 全局唯一缓存标识
  async () => {
    // 自定义请求逻辑
    const res = await $fetch('/api/book/list')
    return res.data
  }
)
</script>
```
#### **实战演练**
- 1，在小册列表页使用 useFetch 获取接口数据并渲染列表。
- 2，故意写错接口地址，测试 error 错误展示效果。
- 3，改用 useAsyncData 重构代码，理解缓存 Key 的作用。

### 4.5 跨域处理与代理配置
当项目对接第三方接口、外部独立后端时，通过 Nuxt 代理和 CORS 配置解决跨域问题。

#### 1. 开发环境接口代理（nuxt.config.ts）
将前端请求路径转发到目标外网接口，解决浏览器跨域。
```ts
// nuxt.config.ts
export default defineNuxtConfig({
  nitro: {
    devProxy: {
      // 匹配 /api-external 开头的请求
      '/api-external': {
        target: 'https://api.example.com', // 目标接口地址
        changeOrigin: true, // 开启跨域
        prependPath: true
      }
    }
  }
})
```
前端调用代理接口
```js
const { data } = await useFetch('/api-external/news')
```

#### 2. 全局 CORS 跨域（允许外部访问本项目接口）
在 server/middleware/ 新建 cors.ts，全局设置跨域响应头：
```ts
// server/middleware/cors.ts
export default defineEventHandler((event) => {
  setResponseHeaders(event, {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Methods': 'GET,POST,PUT,DELETE,OPTIONS',
    'Access-Control-Allow-Headers': 'Content-Type,Authorization'
  })
  // 拦截 OPTIONS 预检请求
  if (event.method === 'OPTIONS') {
    return setResponseStatus(event, 204)
  }
})
```

#### **实战演练**
- 1，配置 devProxy 代理公共测试接口，前端发起请求验证跨域解决。
- 2，配置 CORS 中间件，使用其他域名 / 工具访问本项目接口。

### 4.6 数据缓存与更新策略
合理使用缓存可以减少重复请求、提升页面速度，结合小册业务区分不同缓存方案。

#### 1. 关闭 / 开启请求缓存
useFetch / useAsyncData 默认根据 key 缓存数据，同一页面多次调用只请求一次。
```js
// 关闭缓存，每次都重新请求
const { data } = await useFetch('/api/book/list', {
  cache: false
})
```

#### 2. 手动刷新数据
使用 refresh() 方法主动重新请求接口，适用于发布内容、点赞、刷新列表等场景。
```vue
<template>
  <div>
    <button @click="refreshList">刷新小册列表</button>
    <ul>
      <li v-for="item in data" :key="item.id">{{ item.title }}</li>
    </ul>
  </div>
</template>

<script setup>
const { data, refresh } = await useFetch('/api/book/list')
const refreshList = () => {
  refresh() // 手动重新拉取数据
}
</script>
```

#### 3. 业务分层缓存策略（小册）
- 首页推荐小册：使用 ISR 定时缓存，5~10 分钟自动更新
- 小册详情页：SSR + 短时缓存，保证内容实时性
- 个人中心 / 后台：关闭全局缓存，操作后手动 refresh 刷新
- 静态介绍页：使用 SSG 全静态缓存

#### **实战演练**
- 1，在同一页面多次调用 useFetch，验证默认缓存生效。
- 2，绑定刷新按钮，测试 refresh 方法动态更新列表。
- 3，为不同页面配置对应缓存规则。

### 本章综合实战任务（小册）
- 在小册项目中，完成 server/api 完成小册列表、详情、用户信息整套接口开发。
- 首页、小册列表页使用 useFetch 服务端请求，保障 SEO。
- 个人中心使用 $fetch 客户端请求，配置全局请求拦截。
- 配置接口代理，对接外部测试接口，解决跨域问题。
- 按业务场景为页面配置 SSR/ISR/SSG 及缓存策略。

### 本章总结
- 渲染模式：SSR 实时动态、SSG 极速静态、ISR 增量更新，按需选型。
- Nitro 接口：server/api 快速编写服务端接口，实现全栈一体化开发。
- 客户端请求：$fetch 搭配全局拦截，适用于交互类页面。
- 服务端请求：useFetch / useAsyncData 为内容页面首选，SEO 友好。
- 跨域方案：devProxy 开发代理、cors 中间件实现接口跨域。
- 缓存策略：内置缓存、手动刷新、ISR 时效缓存，全面优化项目性能。
