初识Nuxt3

534 阅读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中提取序列化数据
  • 重建客户端缓存,与服务端保持一致
  • 跳过重复请求,实现无缝水合

客户端导航时:

使用内存中的缓存数据

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