初识Nuxt3

936 阅读12分钟

基础概念

Nuxt3: Vue生态系统的最佳上层框架

框架定位

Nuxt3是基于Vue3的现代全栈通用框架,为开发者提供了结构化的开发方案和丰富的功能,大幅提升开发效率和用户体验。作为Vue生态中的顶级框架,Nuxt3已于2022年底发布稳定版,现已成为Vue项目开发的首选解决方案。

核心优势

1. 现代化开发环境
  • Vue3整合:深度集成Vue3 Composition API和最新特性
  • Vite驱动:默认使用Vite构建工具,提供极速的开发体验(HMR)
  • TypeScript支持:零配置TypeScript支持,完善的类型推断和编辑器集成
  • 文件系统路由:基于目录结构自动生成路由配置
  • 自动导入:组件、API和函数自动导入,减少重复import语句
2. 多种渲染模式
  • 服务端渲染(SSR):改善SEO和首屏加载体验
  • 客户端渲染(CSR/SPA):传统单页应用体验
  • 静态站点生成(SSG):预渲染页面为静态HTML
  • 混合渲染(Hybrid):同一应用中混合使用不同渲染策略
  • 边缘渲染(Edge-Side Rendering):在CDN边缘节点渲染内容
3. 全栈开发能力
  • Nitro服务引擎:强大的服务器端引擎
  • API路由:轻松创建后端API端点
  • 服务器中间件:处理请求响应周期
  • 多平台部署:支持Node.js、Serverless、静态托管等多种部署方式
4. 卓越开发体验
  • Nuxt DevTools:内置开发调试工具,可视化应用结构和性能
  • 模块生态系统:丰富的官方和社区模块(图像优化、内容管理、认证等)
  • 层(Layers):跨项目复用配置、组件和功能
  • 增量静态再生(ISR):结合静态生成和动态更新的优势

架构组成

  • Nuxt3由多个紧密集成的包组成:
  • nuxt:核心引擎,协调各组件并提供基础功能
  • nuxi:命令行工具,用于创建、开发和构建项目
  • nitro:服务端引擎,处理SSR和API路由
  • @nuxt/vite-builder:Vite集成(默认推荐)
  • @nuxt/webpack-builder:Webpack集成(备选方案)
  • @nuxt/kit:模块开发工具包
  • @nuxt/devtools:开发调试工具集

适用场景

Nuxt3适用于各种Web应用开发场景:

  • 内容网站:博客、文档、新闻网站(利用SSG/SSR优化SEO)
  • 电商应用:产品展示与交易平台(结合SSR和API路由)
  • SaaS产品:企业级Web应用(利用全栈能力)
  • 个人作品集:展示类静态网站(利用SSG高效部署)
  • Web应用:任何需要结构化开发的中大型Vue项目

Nuxt 整体架构

Nuxt 框架由一些包组成,它们各有不同作用:

  • 核心引擎:nuxt,实现核心功能,串联所有模块;
  • 打包:@nuxt/vite-builder、@nuxt/webpack-builder;
  • 命令行工具:nuxi,创建、调试、打包项目等;
  • 服务端引擎:nitro,服务端渲染,API 路由;
  • 开发包:@nuxt/kit,用于 Nuxt 模块开发;
  • Nuxt 2 桥:@nuxt/bridge,用于 Nuxt2 项目中使用 Nuxt3 特性。

渲染模式

传统服务端渲染

在传统Web开发中,页面渲染完全由服务器执行。服务器获取数据,组装HTML,再发送给客户端显示。典型技术包括JSP、PHP、ASP.NET等。

特点
  • 服务器负担重,单机并发能力低
  • 前后端代码混合,职责不清晰
  • 开发协作效率低,前后端耦合度高

客户端渲染(CSR)

Vue、React等现代前端框架带来的单页应用(SPA)模式,将渲染工作转移到浏览器执行。

工作流程
  • 浏览器首次获取几乎为空的HTML框架
  • 下载并执行JavaScript代码
  • 通过API获取数据
  • 在客户端动态构建页面DOM
优势
  • 前后端分离,开发效率高
  • 减轻服务器渲染压力
  • 页面切换流畅,用户体验佳
劣势
  • 首屏加载时间长
  • SEO表现差,搜索引擎难以爬取内容
  • 客户端性能要求高

适用场景:后台管理系统、在线工具、SaaS应用等不依赖SEO的交互密集型应用。

通用渲染

Nuxt的通用渲染模式结合了传统服务端渲染和现代SPA的优势。

工作流程
  • 服务器执行Vue组件渲染,生成完整HTML
  • 客户端接收并立即显示内容
  • 同时下载JavaScript
  • 执行"注水(Hydration)"过程,激活静态HTML为可交互应用

注水过程:将静态HTML与JavaScript重新"绑定",恢复事件监听和状态管理,使页面获得交互能力。这是SSR的核心环节。

优势
  • 首屏加载快,用户体验好
  • SEO友好,搜索引擎可抓取完整内容
  • 保留SPA的交互体验
劣势
  • 开发约束增加(服务端环境限制)
  • 服务器资源消耗较高
  • 需要Node.js/Serverless运行环境
适用场景

内容展示类网站、电商平台、企业官网等需要SEO且重视首屏加载速度的应用。

静态站点生成

预先渲染整个网站为静态HTML文件,适合内容变化不频繁的网站。

工作流程
  • 构建时执行一次性渲染,生成所有路由的静态HTML
  • 将静态文件部署到CDN或静态文件服务器
  • 用户访问时直接获取预渲染的HTML
  • 客户端执行"注水"过程,恢复交互性
优势
  • 极速的首屏加载体验
  • 最低的服务器运行成本
  • 最佳的安全性(无动态服务器)
  • SEO友好
劣势
  • 内容更新需要重新构建部署
  • 不适合频繁变化的动态内容
  • 大型网站构建时间长

最新进展:Nuxt3已支持部分增量生成能力(ISR),通过配置routeRules可实现按需重新生成特定页面,无需重建整站。

适用场景

博客、文档站、营销页面等内容相对固定的网站。

混合渲染

Nuxt3的创新功能,允许在同一应用中基于路由规则使用不同渲染策略。

工作原理

通过routeRules配置,为不同路由路径指定不同的渲染模式

// nuxt.config.js
export default defineNuxtConfig({
  routeRules: {
    '/': { ssr: true },                 // 首页使用SSR
    '/products/**': { static: true },   // 产品页使用SSG
    '/admin/**': { ssr: false },        // 管理后台使用CSR
    '/api/**': { cors: true }           // API路由启用CORS
  }
})
优势
  • 最佳性能与灵活性的平衡
  • 按需选择最合适的渲染策略
  • 维护成本低,开发体验一致
可靠性

混合渲染在Nuxt3中已经非常稳定可靠,配置正确时不会带来额外的稳定性问题。

适用场景

复杂应用,如电商平台(静态产品页+动态购物车)、内容网站(静态内容+动态评论系统)等。

边缘渲染

在CDN边缘节点执行轻量级渲染,提供最低延迟的用户体验。

工作原理
  • 将应用部署到全球分布的边缘计算网络
  • 用户请求路由到最近的边缘节点
  • 在边缘节点执行轻量级渲染
  • 返回渲染结果给用户
技术基础

Nuxt3的Nitro服务器引擎支持多运行时环境,包括Cloudflare Workers、Vercel Edge Functions等无需完整Node.js环境的边缘计算平台。

优势
  • 最低的网络延迟(TTFB)
  • 全球一致的快速响应
  • 扩展性好,无需管理服务器
  • 降低主源服务器负载
可靠性

随着Cloudflare、Vercel等平台的成熟,边缘渲染已经达到生产可用的稳定性水平。

适用场景

全球用户分布的应用、对延迟敏感的服务,如实时数据展示、交易系统等。

渲染模式对比总结

渲染模式

首屏性能

SEO友好度

服务器负载

开发复杂度

内容更新

最佳应用场景

CSR

较慢

较差

低

低

实时

管理后台、工具类应用

SSR

快

优秀

中高

中

实时

内容网站、电商、门户

SSG

极快

优秀

极低

低

需重建

博客、文档、营销页

Hybrid

按需优化

按需优化

按需优化

中

灵活

复杂多功能应用

Edge

极快

优秀

分散

中高

实时

全球化服务、高性能应用

Nuxt3的优势在于提供统一的开发体验,让开发者能够根据具体需求灵活选择最合适的渲染策略,实现性能与开发效率的最佳平衡。

开发应用

安装配置部分略过,可以去官网查看:[https://nuxt.com.cn/](https://nuxt.com.cn/)

Pages

Pages是Nuxt应用的基础,通过文件系统自动生成路由。

文件系统路由详解

  • 基本路由:

  • pages/index.vue → /

  • pages/about.vue → /about

  • pages/products/index.vue → /products

  • pages/products/[id].vue → /products/:id

  • 动态路由详解:

  • 单参数:pages/users/[id].vue → /users/:id

  • 多参数:pages/[category]/[product].vue → /:category/:product

  • 可选参数:pages/[[slug]].vue → / 或 /:slug

  • 捕获所有:pages/[...slug].vue → 匹配所有未定义的路由

  • 嵌套路由:

  • 创建同名的目录和文件:

  • pages/users.vue

  • pages/users/index.vue

  • pages/users/profile.vue

  • 父组件需要包含<NuxtPage />组件作为子路由的出口

声明

对于目录结构建议直接浏览官方文档:[https://nuxt.com.cn/](https://nuxt.com.cn/),此处不做赘述。

重要知识介绍

useFetch()

useFetch是Nuxt 3提供的一个组合式函数(composable),用于在Nuxt应用中进行数据获取。它专为Nuxt的服务端渲染(SSR)和客户端水合(hydration)过程设计,能够自动处理跨请求状态管理、请求重复去除、数据缓存等复杂问题。

核心特性
  • 同构请求 - 在服务端和客户端使用相同的API
  • 自动状态管理 - 提供加载状态、错误处理等
  • 智能缓存 - 避免重复请求相同的数据
  • 请求去重 - 多个组件请求相同URL只发一次请求
  • 自动序列化/反序列化 - 服务端到客户端数据传递
  • 类型安全 - 提供完整的TypeScript支持
基本使用语法
const { data: profile, pending, error, refresh } = useFetch('/api/user/profile', {
  // 使用pick选择特定字段
  pick: ['id', 'name', 'email']
})

// data将只包含这三个字段,即使API返回了更多字段
  • data: 请求返回的数据(响应式)
  • pending: 请求是否正在进行中(布尔值)
  • error: 请求错误信息(如果有)
  • refresh: 刷新数据的函数
请求发送逻辑流程

1. 初始化阶段

当组件调用useFetch时,Nuxt会:

  • 检查缓存 - 首先检查相同URL和选项的请求是否已存在
  • 生成唯一键 - 基于URL和选项创建缓存键
  • 初始化状态 - 设置pending = true、data = null、error = null

2. 服务端渲染(SSR)阶段

如果在服务端渲染期间调用useFetch:

  • 发起请求 - 服务端直接发起HTTP请求
  • 等待结果 - 服务端会等待请求完成(这会阻塞页面渲染)
  • 更新状态 - 请求完成后更新data或error
  • 序列化数据 - 将结果序列化并注入到生成的HTML中
  • 状态传递 - 将数据状态传递给客户端,避免客户端重复请求

3. 客户端水合(Hydration)阶段

当浏览器接收到HTML并执行JavaScript:

  • 接收注入数据 - 从HTML中提取序列化的请求结果
  • 恢复状态 - 使用服务端传来的数据恢复data状态
  • 跳过重复请求 - 不会重新发起相同的请求

4. 客户端导航阶段

当用户在应用内导航到新页面时:

  • 检查缓存 - 如果请求已缓存且未过期,直接使用缓存数据
  • 发起请求 - 如无缓存或已过期,发起新请求
  • 更新UI - 根据pending、data、error状态更新界面
  • 缓存结果 - 将新结果存入缓存
详细参数选项
useFetch(url, {
  // 请求方法
  method: 'POST',
  
  // 请求体
  body: { name: '张三' },
  
  // 请求头
  headers: {
    'Content-Type': 'application/json'
  },
  
  // 查询参数
  query: { limit: 10 },
  
  // 唯一键(用于缓存标识)
  key: 'custom-key',
  
  // 转换响应数据的函数
  transform: (data) => data.results,
  
  // 响应类型
  responseType: 'json',
  
  // 仅在服务端获取(不在客户端重新获取)
  server: true,
  
  // 仅在客户端获取(服务端不获取)
  client: false,
  
  // 懒加载模式(手动触发加载)
  lazy: false,
  
  // 请求超时时间(毫秒)
  timeout: 5000,
  
  // 缓存选项
  cache: {
    // 缓存时间(毫秒),超时后自动失效
    maxAge: 60000
  }
})
使用示例
  1. 基本GET请求

  2. 将API请求逻辑提取到composables中

    // composables/useProducts.ts export function useProducts(query = {}) { return useFetch('/api/products', { query, transform: (response) => response.data, default: () => [] }) }

    // 使用 const { data: products } = useProducts({ category: 'electronics' })

  3. 错误处理

$fetch

// 指定返回类型
interface User {
  id: number;
  name: string;
  email: string;
}

// 使用泛型指定响应类型
const user = await $fetch<User>('/api/user/1')

// TypeScript会知道user有id、name和email属性
console.log(user.name) // 类型安全


// 创建取消控制器
const controller = new AbortController()

// 设置超时自动取消
const timeout = setTimeout(() => controller.abort(), 3000)

try {
  const data = await $fetch('/api/slow-operation', {
    signal: controller.signal
  })
  clearTimeout(timeout)
  return data
} catch (error) {
  if (error.name === 'AbortError') {
    console.log('请求被取消')
  }
  throw error
}


// 创建全局拦截器
const customFetch = $fetch.create({
  // 基础URL
  baseURL: 'https://api.example.com',
  
  // 请求拦截
  onRequest({ options }) {
    // 添加认证token
    const token = localStorage.getItem('token')
    if (token) {
      options.headers = options.headers || {}
      options.headers.Authorization = `Bearer ${token}`
    }
  },
  
  // 响应拦截
  onResponse({ response }) {
    // 统一处理响应
    return response._data.result
  },
  
  // 错误拦截
  onResponseError({ response }) {
    // 处理401错误
    if (response.status === 401) {
      // 重定向到登录页
      navigateTo('/login')
    }
  }
})

// 使用自定义fetch实例
const data = await customFetch('/users')
实际应用场景
// 表单提交
async function submitForm() {
  const formData = {
    username: username.value,
    password: password.value,
    rememberMe: rememberMe.value
  }
  
  try {
    const result = await $fetch('/api/login', {
      method: 'POST',
      body: formData
    })
    
    // 登录成功处理
    localStorage.setItem('token', result.token)
    navigateTo('/dashboard')
  } catch (error) {
    // 错误处理
    if (error.response?.status === 401) {
      loginError.value = '用户名或密码错误'
    } else {
      loginError.value = '登录失败,请稍后重试'
    }
  }
}

// 数据删除
async function deleteItem(id) {
  if (!confirm('确定要删除吗?')) return
  
  try {
    await $fetch(`/api/items/${id}`, {
      method: 'DELETE'
    })
    
    // 删除成功
    showNotification('删除成功')
    
    // 刷新列表
    refreshNuxtData('items-list')
  } catch (error) {
    console.error('删除失败', error)
    showNotification('删除失败:' + error.message, 'error')
  }
}


// 实时搜索
const searchQuery = ref('')
const searchResults = ref([])
const isSearching = ref(false)
let searchTimeout

// 防抖搜索函数
async function performSearch() {
  clearTimeout(searchTimeout)
  
  if (!searchQuery.value) {
    searchResults.value = []
    return
  }
  
  searchTimeout = setTimeout(async () => {
    isSearching.value = true
    
    try {
      searchResults.value = await $fetch('/api/search', {
        params: {
          q: searchQuery.value,
          limit: 10
        },
        // 添加短超时,确保响应速度
        timeout: 3000
      })
    } catch (error) {
      console.error('搜索失败', error)
    } finally {
      isSearching.value = false
    }
  }, 300) // 300ms防抖
}

// 监听搜索词变化
watch(searchQuery, performSearch)

useAsyncDate()

缓存机制

useAsyncData的缓存机制是Nuxt数据获取系统的核心特性之一,它可以:

  • 避免重复请求相同数据
  • 在页面导航间保持数据状态
  • 确保服务端渲染(SSR)和客户端水合(hydration)过程中数据一致
  • 提供数据失效和刷新的控制方法
缓存键(Cache Key)的工作原理

每次调用useAsyncData时,必须提供一个唯一的键或生成键的函数:

// 静态键
useAsyncData('users', () => $fetch('/api/users'))

// 动态键(函数形式)
useAsyncData(() => `user-${id.value}`, () => $fetch(`/api/users/${id.value}`))

这个键决定了:

  • 数据在缓存中的存储位置
  • 如何识别相同的数据请求
  • 何时可以重用已缓存的数据
缓存存储位置

useAsyncData的缓存存储在Nuxt的内部状态管理系统中:

  • 服务端: 存储在请求生命周期内的内存中
  • 客户端: 存储在应用实例的内存中
  • SSR传递: 服务端的缓存数据会被序列化并传递到客户端
  • 注意: 这不是持久化缓存,页面刷新会清空缓存数据
缓存的生命周期
  • 创建: 首次调用特定键的useAsyncData时
  • 复用: 后续使用相同键调用时
  • 刷新: 手动调用refresh()或watch触发时
  • 失效: 调用refreshNuxtData(key)或clearNuxtData(key)时
  • 清除: 页面刷新或应用重启时
使用示例
const page = ref(1)

const { data: paginatedUsers } = useAsyncData(
  () => `users-page-${page.value}`,
  () => $fetch('/api/users', { params: { page: page.value } })
)

// 当page变化时,键也会变化
function nextPage() {
  page.value++
  // 自动使用新键`users-page-2`获取并缓存新数据
}
initialCache选项
// 默认使用缓存(如果有)
const { data: posts } = useAsyncData('posts', 
  () => $fetch('/api/posts'),
  { initialCache: true } // 默认值
)

// 强制忽略缓存,总是发起新请求
const { data: freshPosts } = useAsyncData('posts', 
  () => $fetch('/api/posts'),
  { initialCache: false }
)
手动控制缓存
1. 刷新特定数据
const { data, refresh } = useAsyncData('users', () => $fetch('/api/users'))

// 手动刷新,发起新请求并更新缓存
function updateUsers() {
  refresh()
}
2. 全局刷新数据
// 刷新指定键的所有useAsyncData调用
function refreshUserData() {
  refreshNuxtData('users')
}

// 刷新多个数据
function refreshAllData() {
  refreshNuxtData(['users', 'posts', 'comments'])
}
3. 完全清除缓存
// 清除特定键的缓存
clearNuxtData('users')

// 清除所有缓存
clearNuxtData()
缓存与SSR的关系

服务端渲染时:

  • 请求数据并存入服务端缓存
  • 缓存数据被序列化并注入到HTML中

客户端水合时:

  • 从HTML中提取序列化数据
  • 重建客户端缓存,与服务端保持一致
  • 跳过重复请求,实现无缝水合

客户端导航时:

使用内存中的缓存数据

  • 仅在需要时(缓存未命中或失效)发起新请求