概述

Nuxt 4 在 2025 年正式发布,带来了若干架构层面的改进。本文记录了使用 Nuxt 4 构建企业级官网过程中的一些实践经验和思考。

官方文档:https://nuxt.com/docs

Nuxt 4 的关键变化

应用目录结构

Nuxt 4 将前端代码统一到 app/ 目录下,这是与 Nuxt 3 的重要区别:

nuxt-project/
├── app/                    # 应用目录
│   ├── app.vue            # 根组件
│   ├── app.config.ts      # 应用配置
│   ├── components/        # Vue 组件
│   ├── composables/       # 组合式函数
│   ├── layouts/           # 布局组件
│   ├── pages/             # 页面路由
│   ├── plugins/           # 插件
│   └── utils/             # 工具函数
├── content/               # Nuxt Content 内容
├── server/                # 服务端代码
│   ├── api/              # API 路由
│   └── utils/            # 服务端工具
├── public/               # 静态资源
└── nuxt.config.ts        # Nuxt 配置

这种结构让前后端代码边界更加清晰。

兼容性日期

Nuxt 4 引入了 compatibilityDate 配置项,用于控制版本兼容性:

// nuxt.config.ts
export default defineNuxtConfig({
  compatibilityDate: '2025-07-15',
  
  devtools: {
    enabled: process.env.NODE_ENV === 'development'
  },
  
  typescript: {
    strict: true
  }
})

模块配置

模块注册

一个典型的企业级项目通常需要集成多个 Nuxt 模块:

// nuxt.config.ts
const studioEnabled = process.env.NUXT_STUDIO === 'true' 

export default defineNuxtConfig({
  modules: [
    '@nuxt/fonts',           // 字体管理
    '@nuxt/content',         // 内容管理
    studioEnabled ? 'nuxt-studio' : undefined,  // 条件加载
    '@nuxt/image',           // 图片优化
    '@nuxtjs/i18n',          // 国际化
    '@nuxt/ui',              // UI 组件库
    '@nuxt/icon',            // 图标系统
    '@formkit/auto-animate/nuxt',  // 动画
    '@nuxtjs/seo'            // SEO 优化
  ].filter(Boolean)
})

条件加载模块是一个实用技巧。例如 nuxt-studio 在某些开发环境下可能有兼容性问题,通过环境变量控制仅在需要时加载。

常用模块列表

模块用途文档
@nuxt/contentMarkdown 内容管理https://content.nuxt.com
@nuxt/image图片优化https://image.nuxt.com
@nuxt/uiUI 组件库https://ui.nuxt.com
@nuxtjs/i18n国际化https://i18n.nuxtjs.org
@nuxtjs/seoSEO 优化https://seo.nuxtjs.org
nuxt-studio可视化编辑https://nuxt.studio

更多模块可在 Nuxt Modules 查找。

渲染策略

Nuxt 4 支持多种渲染模式,理解它们的差异对于构建高性能应用很重要。

预渲染(Prerendering)

预渲染在构建时生成静态 HTML,适合内容相对固定的页面:

// nuxt.config.ts
export default defineNuxtConfig({
  routeRules: {
    '/': { prerender: true },
    '/about': { prerender: true },
    '/sponsors': { prerender: true }
  }
})

预渲染的优势:

  • 静态文件直接返回,无需服务端计算
  • 可缓存于 CDN 边缘节点
  • 不依赖数据库或外部服务

ISR(增量静态再生)

ISR 是预渲染和动态渲染的平衡,适合内容定期更新的场景:

routeRules: {
  // 新闻列表:1 小时缓存
  '/news': process.env.NODE_ENV === 'development' 
    ? {} 
    : { swr: 3600 },
  '/news/**': process.env.NODE_ENV === 'development' 
    ? {} 
    : { swr: 3600 },
    
  // API 路由缓存
  '/api/news': { swr: 1800 },   // 30 分钟
  '/api/cars': { swr: 7200 },   // 2 小时
}

ISR 的工作流程:

  1. 首次请求时,服务端渲染页面并缓存
  2. 在 swr 指定的时间内,直接返回缓存内容
  3. 缓存过期后,下一次请求仍返回旧缓存,同时在后台重新渲染
  4. 后台渲染完成后,更新缓存供后续请求使用
注意:开发环境下应禁用 ISR,否则调试时可能命中陈旧缓存,导致修改不生效。

预渲染配置注意事项

当使用 Nuxt Content v3 时,预渲染配置需要注意:

nitro: {
  prerender: {
    concurrency: 1,      // 强制串行处理
    failOnError: true,
    crawlLinks: true,
    ignore: ['/_ipx/**'] // 排除动态图片路由
  }
}

concurrency: 1 强制串行预渲染,这是因为 Nuxt Content v3 使用 SQLite 作为底层存储,多进程同时访问 SQLite 可能导致内存指针错误。

组件架构

全局组件注册

Nuxt 支持自动导入组件,通过配置可以实现不同的注册策略:

// nuxt.config.ts
export default defineNuxtConfig({
  components: [
    {
      path: '~/components/content',
      global: true  // 全局注册
    },
    '~/components'  // 按需导入
  ]
})

Composables 模式

Composables 是 Vue 3 组合式 API 的核心概念,Nuxt 提供了 auto-imports 机制:

// app/composables/useTheme.ts
export const useTheme = () => {
  const currentTheme = ref<'light' | 'dark'>('dark')
  
  const setTheme = (theme: 'light' | 'dark') => {
    currentTheme.value = theme
    document.documentElement.setAttribute('data-theme', theme)
    localStorage.setItem('theme', theme)
    
    if (theme === 'dark') {
      document.documentElement.classList.add('dark')
    } else {
      document.documentElement.classList.remove('dark')
    }
  }
  
  const toggleTheme = () => {
    setTheme(currentTheme.value === 'dark' ? 'light' : 'dark')
  }
  
  const initTheme = () => {
    const saved = localStorage.getItem('theme') as 'light' | 'dark' | null
    setTheme(saved || 'dark')
  }
  
  return {
    currentTheme: readonly(currentTheme),
    setTheme,
    toggleTheme,
    initTheme
  }
}

使用时无需导入:

<script setup lang="ts">
const { currentTheme, toggleTheme, initTheme } = useTheme()

onMounted(() => {
  initTheme()
})
</script>

防止主题闪烁

主题切换时,如果等 JavaScript 加载后再应用主题,用户会看到短暂的样式闪烁。解决方案是在 <head> 中注入内联脚本:

// nuxt.config.ts
app: {
  head: {
    script: [
      {
        innerHTML: `(function(){
          var d = document.documentElement;
          try {
            var t = localStorage.getItem('theme') || 'dark';
            d.setAttribute('data-theme', t);
            if (t === 'dark') d.classList.add('dark');
          } catch(e) {}
        })()`,
        type: 'text/javascript',
        tagPosition: 'head'
      }
    ]
  }
}

这段代码在 HTML 解析时立即执行,确保首屏渲染时主题已经正确设置。

服务端 API

API 路由定义

Nuxt 的 server/api/ 目录下可以定义 API 路由:

// server/api/news.ts
export default defineEventHandler(async (event) => {
  const query = getQuery(event)
  const category = query.category as string | undefined
  const search = (query.search as string)?.trim().toLowerCase()
  const page = Math.max(1, Number(query.page) || 1)
  const pageSize = Math.min(50, Math.max(1, Number(query.pageSize) || 10))

  // 获取数据...
  const data = await fetchData(category, search, page, pageSize)

  return {
    data: data.items,
    total: data.total,
    page,
    pageSize
  }
})

前端调用

使用 Nuxt 提供的 useFetchuseAsyncData

<script setup lang="ts">
const currentPage = ref(1)
const itemsPerPage = ref(9)
const selectedCategory = ref('all')
const searchQuery = ref('')

const { data, status, refresh } = await useAsyncData('news-list', 
  () => $fetch('/api/news', {
    query: {
      page: currentPage.value,
      pageSize: itemsPerPage.value,
      category: selectedCategory.value,
      search: searchQuery.value
    }
  }),
  {
    watch: [currentPage, itemsPerPage, selectedCategory, searchQuery]
  }
)
</script>

watch 选项让任何依赖变化时自动重新获取数据。

国际化

配置 @nuxtjs/i18n

// nuxt.config.ts
i18n: {
  locales: [
    { code: 'en', file: 'en.json', name: 'English' },
    { code: 'zh', file: 'zh.json', name: '简体中文' }
  ],
  defaultLocale: 'zh',
  strategy: 'prefix_except_default',
  detectBrowserLanguage: {
    useCookie: true,
    cookieKey: 'i18n_redirected',
    fallbackLocale: 'zh'
  }
}

strategy: 'prefix_except_default' 表示默认语言不添加 URL 前缀,其他语言路由以对应前缀开头。

使用翻译

<template>
  <h1>{{ t('heroTitle') }}</h1>
  <p>{{ t('heroDesc') }}</p>
</template>

<script setup lang="ts">
const { t } = useI18n()
</script>

SEO 配置

使用 @nuxtjs/seo

// nuxt.config.ts
site: {
  url: 'https://example.com',
  name: 'Site Name',
  description: 'Site description',
  defaultLocale: 'zh'
},
sitemap: {
  zeroRuntime: true
},
robots: {
  allow: '/',
  sitemap: 'https://example.com/sitemap.xml'
}

页面级 SEO

<script setup lang="ts">
useHead({
  title: () => t('pageTitle')
})

useSeoMeta({
  description: () => t('pageDescription'),
  ogTitle: () => t('pageTitle'),
  ogDescription: () => t('pageDescription'),
  ogImage: 'https://example.com/cover.jpg'
})
</script>

小结

Nuxt 4 提供了完整的全栈开发能力,合理的架构设计是项目可维护性的基础。渲染策略的选择应根据页面特性决定:静态内容适合预渲染,动态内容适合 ISR 或 SSR。Composables 模式和自动导入机制让代码组织更加清晰。


参考资料: