Nuxt.js基础与进阶

Nuxt.js基础与进阶

Nuxt.js 简介

Nuxt.js 是一个基于 Vue.js 的开源框架,专门用于构建服务端渲染(SSR)、静态站点生成(SSG)和单页应用(SPA)。它提供了一套约定大于配置的开发体系,大大简化了 Vue 应用的构建和部署过程。

核心特性

  • 自动路由生成:基于文件系统的路由
  • 服务端渲染:内置 SSR 支持,提升 SEO 和首屏性能
  • 静态站点生成:支持预渲染静态页面
  • 代码分割:自动代码分割和懒加载
  • 插件生态:丰富的模块和插件系统
  • 开发体验:热重载、错误页面、调试工具

Nuxt.js 架构原理

graph TD A[用户请求] --> B{渲染模式判断} B -->|SSR| C[服务端渲染] B -->|SPA| D[客户端渲染] B -->|SSG| E[静态页面] C --> F[Vue组件执行] F --> G[数据预取] G --> H[HTML生成] H --> I[返回完整页面] D --> J[加载Vue应用] J --> K[客户端路由] K --> L[动态渲染] E --> M[预构建HTML] M --> N[CDN分发]

Nuxt.js 核心概念

1. 项目结构

javascript 复制代码
// 标准 Nuxt.js 项目结构
my-nuxt-app/
├── assets/          // 未编译资源文件
├── components/      // Vue 组件
├── layouts/         // 布局组件
├── middleware/      // 中间件
├── pages/          // 页面组件(自动生成路由)
├── plugins/        // Vue 插件
├── static/         // 静态文件
├── store/          // Vuex 状态管理
├── nuxt.config.js  // Nuxt 配置文件
└── package.json

2. 页面组件

javascript 复制代码
// pages/index.vue - 首页组件
<template>
  <div class="container">
    <h1>{{ title }}</h1>
    <p>欢迎访问 Nuxt.js 应用</p>
  </div>
</template>

<script>
export default {
  name: 'HomePage',
  
  // 页面头部信息
  head() {
    return {
      title: 'Nuxt.js 首页',
      meta: [
        { hid: 'description', name: 'description', content: '这是首页描述' }
      ]
    }
  },
  
  // 服务端数据预取
  async asyncData({ $axios }) {
    const { data } = await $axios.get('/api/content')
    return {
      title: data.title
    }
  }
}
</script>

3. 布局系统

javascript 复制代码
// layouts/default.vue - 默认布局
<template>
  <div class="app">
    <header class="header">
      <nav>
        <NuxtLink to="/">首页</NuxtLink>
        <NuxtLink to="/about">关于</NuxtLink>
      </nav>
    </header>
    
    <main class="main">
      <!-- 页面内容插槽 -->
      <Nuxt />
    </main>
    
    <footer class="footer">
      <p>&copy; 2024 Nuxt.js 应用</p>
    </footer>
  </div>
</template>

<script>
export default {
  name: 'DefaultLayout'
}
</script>

4. 中间件系统

javascript 复制代码
// middleware/auth.js - 身份验证中间件
export default function ({ store, redirect }) {
  // 检查用户是否已登录
  if (!store.state.auth.user) {
    return redirect('/login')
  }
}

// pages/dashboard.vue - 使用中间件的页面
<template>
  <div>
    <h1>用户仪表板</h1>
  </div>
</template>

<script>
export default {
  middleware: 'auth' // 应用认证中间件
}
</script>

渲染模式详解

1. 服务端渲染(SSR)

javascript 复制代码
// nuxt.config.js - SSR 模式配置
export default {
  // SSR 模式(默认)
  ssr: true,
  
  // 服务端渲染配置
  render: {
    // 资源提示
    resourceHints: true,
    // 内联关键CSS
    inlineStyles: true
  }
}

// 页面组件中的 SSR 数据预取
export default {
  async asyncData({ params, $axios }) {
    // 服务端执行,预取数据
    const post = await $axios.$get(`/api/posts/${params.id}`)
    return { post }
  },
  
  async fetch({ store, params }) {
    // 服务端和客户端都会执行
    await store.dispatch('posts/fetchPost', params.id)
  }
}

2. 静态站点生成(SSG)

javascript 复制代码
// nuxt.config.js - SSG 配置
export default {
  // 静态生成模式
  target: 'static',
  
  // 生成配置
  generate: {
    // 动态路由生成
    routes() {
      return axios.get('/api/posts')
        .then(res => res.data.map(post => `/posts/${post.id}`))
    },
    
    // 生成间隔
    interval: 100,
    
    // 并发数
    concurrency: 10
  }
}

3. 单页应用(SPA)

javascript 复制代码
// nuxt.config.js - SPA 模式配置
export default {
  // 禁用 SSR,启用 SPA 模式
  ssr: false,
  
  // SPA 配置
  spa: {
    // 加载指示器
    loading: '~/components/LoadingIndicator.vue'
  }
}

动态路由和嵌套路由

路由生成机制

Nuxt.js 基于文件系统自动生成路由,无需手动配置路由表。

graph TD A[pages目录结构] --> B[自动路由生成] B --> C[静态路由] B --> D[动态路由] B --> E[嵌套路由] C --> F["about.vue → /about"] D --> G["_id.vue → /:id"] E --> H["user/_id/profile.vue → /user/:id/profile"]

1. 基础路由

javascript 复制代码
// 文件结构和对应路由
pages/
├── index.vue        // → /
├── about.vue        // → /about
├── contact.vue      // → /contact
└── blog/
    ├── index.vue    // → /blog
    └── archive.vue  // → /blog/archive

2. 动态路由

单参数动态路由
javascript 复制代码
// pages/user/_id.vue - 用户详情页
<template>
  <div class="user-profile">
    <h1>用户: {{ user.name }}</h1>
    <p>ID: {{ $route.params.id }}</p>
  </div>
</template>

<script>
export default {
  async asyncData({ params, $axios }) {
    // 根据路由参数获取用户数据
    const user = await $axios.$get(`/api/users/${params.id}`)
    return { user }
  },
  
  // 参数校验
  validate({ params }) {
    return /^\d+$/.test(params.id)
  }
}
</script>
多参数动态路由
javascript 复制代码
// pages/category/_type/_id.vue - 分类文章页
<template>
  <div>
    <h1>{{ article.title }}</h1>
    <p>分类: {{ $route.params.type }}</p>
    <div v-html="article.content"></div>
  </div>
</template>

<script>
export default {
  async asyncData({ params, $axios }) {
    const { type, id } = params
    const article = await $axios.$get(`/api/articles/${type}/${id}`)
    return { article }
  },
  
  // 路径参数: /category/tech/123
  // params: { type: 'tech', id: '123' }
}
</script>
可选动态路由
javascript 复制代码
// pages/shop/_category/_product.vue - 可选分类的商品页
<template>
  <div>
    <h1>{{ product.name }}</h1>
    <p v-if="category">分类: {{ category }}</p>
  </div>
</template>

<script>
export default {
  async asyncData({ params }) {
    const { category, product } = params
    
    // 处理可选参数
    const productData = await fetchProduct(product, category)
    
    return { 
      product: productData,
      category 
    }
  }
}
</script>

// 支持路由:
// /shop/electronics/laptop  → { category: 'electronics', product: 'laptop' }
// /shop/laptop              → { product: 'laptop' }

3. 嵌套路由

基础嵌套路由
javascript 复制代码
// 文件结构
pages/
├── user/
│   ├── index.vue      // → /user
│   ├── _id/
│   │   ├── index.vue  // → /user/:id
│   │   ├── profile.vue // → /user/:id/profile
│   │   └── settings.vue // → /user/:id/settings
│   └── create.vue     // → /user/create

// pages/user.vue - 父级路由组件
<template>
  <div class="user-layout">
    <nav class="user-nav">
      <NuxtLink :to="`/user/${userId}`">概览</NuxtLink>
      <NuxtLink :to="`/user/${userId}/profile`">个人信息</NuxtLink>
      <NuxtLink :to="`/user/${userId}/settings`">设置</NuxtLink>
    </nav>
    
    <!-- 子路由内容 -->
    <NuxtChild :user="user" />
  </div>
</template>

<script>
export default {
  async asyncData({ params }) {
    const user = await fetchUser(params.id)
    return { user, userId: params.id }
  }
}
</script>
复杂嵌套路由
javascript 复制代码
// pages/admin/_module/_action.vue - 管理后台嵌套路由
<template>
  <div class="admin-panel">
    <AdminSidebar :module="$route.params.module" />
    <div class="admin-content">
      <component 
        :is="actionComponent" 
        :module="$route.params.module"
        :action="$route.params.action"
      />
    </div>
  </div>
</template>

<script>
export default {
  computed: {
    actionComponent() {
      const { module, action } = this.$route.params
      return () => import(`~/components/admin/${module}/${action}.vue`)
    }
  },
  
  middleware: 'admin',
  
  // 支持路由如: /admin/users/list, /admin/posts/edit
}
</script>

4. 路由参数和查询

路由参数处理
javascript 复制代码
// pages/search/_query.vue - 搜索结果页
<template>
  <div class="search-results">
    <h1>搜索: "{{ $route.params.query }}"</h1>
    <div class="filters">
      <select v-model="category" @change="updateQuery">
        <option value="">所有分类</option>
        <option value="tech">技术</option>
        <option value="design">设计</option>
      </select>
    </div>
    
    <div class="results">
      <div v-for="item in results" :key="item.id">
        {{ item.title }}
      </div>
    </div>
  </div>
</template>

<script>
export default {
  async asyncData({ params, query }) {
    const searchQuery = params.query
    const category = query.category || ''
    
    const results = await searchContent(searchQuery, {
      category,
      page: query.page || 1,
      limit: query.limit || 10
    })
    
    return { results, category }
  },
  
  methods: {
    updateQuery() {
      this.$router.push({
        path: this.$route.path,
        query: { 
          ...this.$route.query,
          category: this.category,
          page: 1 // 重置页码
        }
      })
    }
  },
  
  watchQuery: ['category', 'page'] // 监听查询参数变化
}
</script>

5. 路由守卫和中间件

页面级中间件
javascript 复制代码
// middleware/auth.js - 认证中间件
export default function ({ store, redirect, route }) {
  // 检查登录状态
  if (!store.state.auth.user) {
    // 保存当前路径,登录后跳转回来
    return redirect('/login?redirect=' + route.fullPath)
  }
  
  // 检查权限
  const requiredRole = route.meta?.role
  if (requiredRole && !store.state.auth.user.roles.includes(requiredRole)) {
    throw new Error('权限不足')
  }
}

// pages/admin/_id.vue - 使用中间件
export default {
  middleware: ['auth'],
  
  meta: {
    role: 'admin' // 需要管理员权限
  }
}
全局中间件
javascript 复制代码
// middleware/analytics.js - 统计中间件
export default function ({ route }) {
  // 页面访问统计
  if (process.client) {
    gtag('config', 'GA_MEASUREMENT_ID', {
      page_path: route.fullPath
    })
  }
}

// nuxt.config.js - 注册全局中间件
export default {
  router: {
    middleware: 'analytics'
  }
}

6. 程序化导航

javascript 复制代码
// 在组件中使用程序化导航
export default {
  methods: {
    // 基础导航
    goToUser(userId) {
      this.$router.push(`/user/${userId}`)
    },
    
    // 带查询参数导航
    searchWithFilters(query, filters) {
      this.$router.push({
        path: `/search/${query}`,
        query: filters
      })
    },
    
    // 替换当前路由
    replaceRoute() {
      this.$router.replace('/new-path')
    },
    
    // 历史导航
    goBack() {
      this.$router.go(-1)
    }
  }
}

7. 路由生成和预取

javascript 复制代码
// nuxt.config.js - 动态路由生成配置
export default {
  generate: {
    routes() {
      // 生成用户页面路由
      return axios.get('/api/users')
        .then(res => {
          return res.data.map(user => ({
            route: `/user/${user.id}`,
            payload: user // 预设数据
          }))
        })
    }
  }
}

// 使用预设数据
export default {
  async asyncData({ params, payload }) {
    // 优先使用预设数据,减少API调用
    if (payload) {
      return { user: payload }
    }
    
    // 回退到API获取
    const user = await $axios.$get(`/api/users/${params.id}`)
    return { user }
  }
}

SEO优化

SEO优化流程

graph TD A[页面请求] --> B[Meta标签生成] B --> C[结构化数据] C --> D[Open Graph标签] D --> E[Sitemap生成] E --> F[搜索引擎抓取] F --> G[页面索引] G --> H[搜索结果展示]

1. 页面Meta信息

动态Meta标签
javascript 复制代码
// pages/blog/_slug.vue - 博客文章页面
<template>
  <article>
    <h1>{{ post.title }}</h1>
    <div v-html="post.content"></div>
  </article>
</template>

<script>
export default {
  async asyncData({ params, $axios }) {
    const post = await $axios.$get(`/api/posts/${params.slug}`)
    return { post }
  },
  
  head() {
    const post = this.post
    return {
      title: post.title,
      meta: [
        { 
          hid: 'description', 
          name: 'description', 
          content: post.summary 
        },
        { 
          hid: 'keywords', 
          name: 'keywords', 
          content: post.tags.join(', ') 
        },
        // Open Graph 标签
        { 
          hid: 'og:title', 
          property: 'og:title', 
          content: post.title 
        },
        { 
          hid: 'og:description', 
          property: 'og:description', 
          content: post.summary 
        },
        { 
          hid: 'og:image', 
          property: 'og:image', 
          content: post.image 
        },
        { 
          hid: 'og:url', 
          property: 'og:url', 
          content: `https://example.com/blog/${post.slug}` 
        },
        // Twitter Card 标签
        { 
          hid: 'twitter:card', 
          name: 'twitter:card', 
          content: 'summary_large_image' 
        },
        { 
          hid: 'twitter:title', 
          name: 'twitter:title', 
          content: post.title 
        }
      ],
      link: [
        { 
          rel: 'canonical', 
          href: `https://example.com/blog/${post.slug}` 
        }
      ]
    }
  }
}
</script>
全局Meta配置
javascript 复制代码
// nuxt.config.js - 全局SEO配置
export default {
  head: {
    titleTemplate: '%s | 我的网站',
    htmlAttrs: {
      lang: 'zh-CN'
    },
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
      { 
        hid: 'description', 
        name: 'description', 
        content: '这是我的网站描述' 
      },
      // 默认 Open Graph 标签
      { property: 'og:type', content: 'website' },
      { property: 'og:locale', content: 'zh_CN' },
      { property: 'og:site_name', content: '我的网站' }
    ],
    link: [
      { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
    ]
  }
}

2. 结构化数据

JSON-LD 结构化数据
javascript 复制代码
// components/StructuredData.vue - 结构化数据组件
<template>
  <div>
    <!-- JSON-LD 结构化数据 -->
    <script type="application/ld+json" v-html="jsonLD"></script>
  </div>
</template>

<script>
export default {
  props: {
    type: String,
    data: Object
  },
  
  computed: {
    jsonLD() {
      const schemas = {
        article: this.articleSchema,
        product: this.productSchema,
        organization: this.organizationSchema
      }
      
      return JSON.stringify(schemas[this.type] || {})
    },
    
    articleSchema() {
      return {
        '@context': 'https://schema.org',
        '@type': 'Article',
        headline: this.data.title,
        description: this.data.summary,
        image: this.data.image,
        author: {
          '@type': 'Person',
          name: this.data.author.name
        },
        publisher: {
          '@type': 'Organization',
          name: '我的网站',
          logo: {
            '@type': 'ImageObject',
            url: 'https://example.com/logo.png'
          }
        },
        datePublished: this.data.publishedAt,
        dateModified: this.data.updatedAt
      }
    },
    
    productSchema() {
      return {
        '@context': 'https://schema.org',
        '@type': 'Product',
        name: this.data.name,
        description: this.data.description,
        image: this.data.images,
        offers: {
          '@type': 'Offer',
          price: this.data.price,
          priceCurrency: 'CNY',
          availability: 'https://schema.org/InStock'
        }
      }
    }
  }
}
</script>
使用结构化数据
javascript 复制代码
// pages/blog/_slug.vue - 在页面中使用结构化数据
<template>
  <div>
    <StructuredData type="article" :data="post" />
    <article>
      <h1>{{ post.title }}</h1>
      <div v-html="post.content"></div>
    </article>
  </div>
</template>

3. 网站地图生成

自动生成Sitemap
javascript 复制代码
// nuxt.config.js - Sitemap 配置
export default {
  modules: [
    '@nuxtjs/sitemap'
  ],
  
  sitemap: {
    hostname: 'https://example.com',
    gzip: true,
    exclude: [
      '/admin/**',
      '/private'
    ],
    routes: async () => {
      // 动态生成路由
      const { data } = await axios.get('/api/posts')
      return data.map(post => `/blog/${post.slug}`)
    },
    defaults: {
      changefreq: 'daily',
      priority: 1,
      lastmod: new Date()
    }
  }
}
自定义Sitemap
javascript 复制代码
// 手动生成 sitemap.xml
export default {
  generate: {
    async done(generator) {
      // 生成完成后创建sitemap
      const sitemap = await generateSitemap()
      await fs.writeFile('dist/sitemap.xml', sitemap)
    }
  }
}

async function generateSitemap() {
  const urls = [
    { loc: '/', priority: 1.0 },
    { loc: '/about', priority: 0.8 }
  ]
  
  // 添加动态页面
  const posts = await fetchPosts()
  posts.forEach(post => {
    urls.push({
      loc: `/blog/${post.slug}`,
      lastmod: post.updatedAt,
      priority: 0.6
    })
  })
  
  return generateXML(urls)
}

4. 性能优化与SEO

关键资源优化
javascript 复制代码
// nuxt.config.js - 性能优化配置
export default {
  render: {
    // 资源提示
    resourceHints: true,
    // 内联关键CSS
    inlineStyles: true,
    // 预加载策略
    bundleRenderer: {
      shouldPreload: (file, type) => {
        return ['script', 'style', 'font'].includes(type)
      }
    }
  },
  
  // 图片优化
  image: {
    // 响应式图片
    responsive: {
      placeholder: true,
      quality: 80
    }
  }
}
懒加载和预取
javascript 复制代码
// components/LazyImage.vue - 懒加载图片组件
<template>
  <div class="lazy-image">
    <img
      v-if="loaded"
      :src="src"
      :alt="alt"
      @load="onLoad"
    >
    <div v-else class="placeholder">
      Loading...
    </div>
  </div>
</template>

<script>
export default {
  props: ['src', 'alt'],
  data() {
    return { loaded: false }
  },
  
  mounted() {
    // 使用 Intersection Observer 实现懒加载
    const observer = new IntersectionObserver((entries) => {
      if (entries[0].isIntersecting) {
        this.loaded = true
        observer.disconnect()
      }
    })
    
    observer.observe(this.$el)
  }
}
</script>

5. 多语言SEO

国际化配置
javascript 复制代码
// nuxt.config.js - 多语言配置
export default {
  modules: [
    '@nuxtjs/i18n'
  ],
  
  i18n: {
    locales: [
      { code: 'zh', iso: 'zh-CN', file: 'zh.js' },
      { code: 'en', iso: 'en-US', file: 'en.js' }
    ],
    defaultLocale: 'zh',
    strategy: 'prefix_except_default',
    seo: true, // 自动生成hreflang标签
    baseUrl: 'https://example.com'
  }
}
多语言页面SEO
javascript 复制代码
// pages/blog/_slug.vue - 多语言SEO
export default {
  head() {
    const post = this.post
    const locale = this.$i18n.locale
    
    return {
      title: post.title[locale],
      meta: [
        { 
          hid: 'description', 
          name: 'description', 
          content: post.summary[locale] 
        },
        { 
          hid: 'og:locale', 
          property: 'og:locale', 
          content: locale === 'zh' ? 'zh_CN' : 'en_US' 
        }
      ],
      link: [
        // 规范链接
        { 
          rel: 'canonical', 
          href: `https://example.com${this.localePath(this.$route)}` 
        },
        // 多语言链接
        { 
          rel: 'alternate', 
          hreflang: 'zh-CN', 
          href: `https://example.com${this.switchLocalePath('zh')}` 
        },
        { 
          rel: 'alternate', 
          hreflang: 'en-US', 
          href: `https://example.com${this.switchLocalePath('en')}` 
        }
      ]
    }
  }
}

6. SEO监控和分析

Google Analytics 集成
javascript 复制代码
// plugins/gtag.client.js - GA4 集成
export default ({ $gtag, app }) => {
  // 页面浏览追踪
  app.router.afterEach((to) => {
    $gtag('config', 'GA_MEASUREMENT_ID', {
      page_path: to.fullPath,
      page_title: document.title
    })
  })
}

// nuxt.config.js
export default {
  plugins: [
    { src: '~/plugins/gtag.client.js', mode: 'client' }
  ],
  
  head: {
    script: [
      {
        async: true,
        src: 'https://www.googletagmanager.com/gtag/js?id=GA_MEASUREMENT_ID'
      }
    ]
  }
}
SEO检查工具
javascript 复制代码
// utils/seo-checker.js - SEO检查工具
export function checkSEO(page) {
  const issues = []
  
  // 检查标题长度
  if (!page.title || page.title.length < 30 || page.title.length > 60) {
    issues.push('标题长度不合适(建议30-60字符)')
  }
  
  // 检查描述
  if (!page.description || page.description.length < 120 || page.description.length > 160) {
    issues.push('描述长度不合适(建议120-160字符)')
  }
  
  // 检查关键词
  if (!page.keywords || page.keywords.length === 0) {
    issues.push('缺少关键词')
  }
  
  // 检查图片alt属性
  const images = page.querySelectorAll('img')
  images.forEach(img => {
    if (!img.alt) {
      issues.push(`图片缺少alt属性: ${img.src}`)
    }
  })
  
  return issues
}

7. Schema.org 标记

完整的页面Schema
javascript 复制代码
// mixins/schema.js - Schema标记混入
export default {
  head() {
    return {
      script: [
        {
          type: 'application/ld+json',
          innerHTML: JSON.stringify(this.pageSchema)
        }
      ]
    }
  },
  
  computed: {
    pageSchema() {
      return {
        '@context': 'https://schema.org',
        '@graph': [
          this.websiteSchema,
          this.organizationSchema,
          this.breadcrumbSchema,
          this.pageSpecificSchema
        ]
      }
    },
    
    websiteSchema() {
      return {
        '@type': 'WebSite',
        '@id': 'https://example.com/#website',
        url: 'https://example.com',
        name: '我的网站',
        potentialAction: {
          '@type': 'SearchAction',
          target: 'https://example.com/search?q={search_term_string}',
          'query-input': 'required name=search_term_string'
        }
      }
    },
    
    breadcrumbSchema() {
      const breadcrumbs = this.generateBreadcrumbs()
      return {
        '@type': 'BreadcrumbList',
        itemListElement: breadcrumbs.map((item, index) => ({
          '@type': 'ListItem',
          position: index + 1,
          name: item.name,
          item: item.url
        }))
      }
    }
  }
}

部署 Nuxt.js 应用

部署流程

graph TD A[开发完成] --> B{部署模式选择} B -->|SSR| C[服务端渲染部署] B -->|SSG| D[静态站点生成] B -->|SPA| E[单页应用部署] C --> F[Node.js服务器] F --> G[PM2进程管理] G --> H[Nginx反向代理] D --> I[静态文件生成] I --> J[CDN分发] E --> K[构建SPA] K --> L[静态服务器]

1. 静态站点生成(SSG)部署

生成静态站点
javascript 复制代码
// nuxt.config.js - SSG 配置
export default {
  target: 'static',
  
  generate: {
    // 并发生成数
    concurrency: 25,
    
    // 生成间隔
    interval: 100,
    
    // 动态路由生成
    routes: async () => {
      const routes = []
      
      // 生成博客文章路由
      const posts = await fetchPosts()
      posts.forEach(post => {
        routes.push(`/blog/${post.slug}`)
      })
      
      // 生成用户页面路由
      const users = await fetchUsers()
      users.forEach(user => {
        routes.push(`/user/${user.id}`)
      })
      
      return routes
    },
    
    // 排除路径
    exclude: [
      /^\/admin/, // 排除管理页面
      /^\/api/    // 排除API路由
    ]
  }
}
Netlify 部署
javascript 复制代码
// netlify.toml - Netlify 配置
[build]
  command = "npm run generate"
  publish = "dist"

[build.environment]
  NODE_VERSION = "16"

[[redirects]]
  from = "/api/*"
  to = "https://api.example.com/:splat"
  status = 200

[[headers]]
  for = "/*"
  [headers.values]
    X-Frame-Options = "DENY"
    X-XSS-Protection = "1; mode=block"
    X-Content-Type-Options = "nosniff"

# package.json 脚本
{
  "scripts": {
    "build": "nuxt build",
    "generate": "nuxt generate",
    "deploy": "npm run generate && netlify deploy --prod --dir=dist"
  }
}
GitHub Pages 部署
javascript 复制代码
// .github/workflows/deploy.yml - GitHub Actions
name: Deploy to GitHub Pages

on:
  push:
    branches: [ main ]

jobs:
  deploy:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v2
    
    - name: Setup Node.js
      uses: actions/setup-node@v2
      with:
        node-version: '16'
        cache: 'npm'
    
    - name: Install dependencies
      run: npm ci
    
    - name: Generate static files
      run: npm run generate
      env:
        NUXT_ENV_BASE_URL: https://username.github.io/repository-name
    
    - name: Deploy to GitHub Pages
      uses: peaceiris/actions-gh-pages@v3
      with:
        github_token: ${{ secrets.GITHUB_TOKEN }}
        publish_dir: ./dist

# nuxt.config.js - GitHub Pages 配置
export default {
  target: 'static',
  router: {
    base: process.env.NODE_ENV === 'production' ? '/repository-name/' : '/'
  }
}

2. 服务端渲染(SSR)部署

PM2 进程管理
javascript 复制代码
// ecosystem.config.js - PM2 配置
module.exports = {
  apps: [
    {
      name: 'nuxt-app',
      exec_mode: 'cluster',
      instances: 'max',
      script: './node_modules/nuxt/bin/nuxt.js',
      args: 'start',
      env: {
        NODE_ENV: 'production',
        PORT: 3000
      },
      error_file: './logs/err.log',
      out_file: './logs/out.log',
      log_file: './logs/combined.log',
      time: true
    }
  ]
}

// 部署脚本
{
  "scripts": {
    "build": "nuxt build",
    "start": "nuxt start",
    "pm2:start": "pm2 start ecosystem.config.js",
    "pm2:stop": "pm2 stop ecosystem.config.js",
    "pm2:reload": "pm2 reload ecosystem.config.js"
  }
}
Docker 部署
dockerfile 复制代码
# Dockerfile
FROM node:16-alpine

# 设置工作目录
WORKDIR /app

# 复制 package.json 和 package-lock.json
COPY package*.json ./

# 安装依赖
RUN npm ci --only=production && npm cache clean --force

# 复制源代码
COPY . .

# 构建应用
RUN npm run build

# 暴露端口
EXPOSE 3000

# 启动应用
CMD ["npm", "start"]
yaml 复制代码
# docker-compose.yml
version: '3.8'

services:
  nuxt-app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
    restart: unless-stopped
    
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
      - ./ssl:/etc/ssl
    depends_on:
      - nuxt-app
    restart: unless-stopped
Nginx 配置
nginx 复制代码
# nginx.conf
upstream nuxt {
    server nuxt-app:3000;
}

server {
    listen 80;
    server_name example.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name example.com;
    
    ssl_certificate /etc/ssl/cert.pem;
    ssl_certificate_key /etc/ssl/key.pem;
    
    # Gzip 压缩
    gzip on;
    gzip_vary on;
    gzip_types
        text/plain
        text/css
        text/xml
        text/javascript
        application/javascript
        application/xml+rss
        application/json;
    
    # 静态资源缓存
    location /_nuxt/ {
        alias /app/.nuxt/dist/client/;
        expires 1y;
        add_header Cache-Control "public, immutable";
        access_log off;
    }
    
    # 代理到 Nuxt.js 应用
    location / {
        proxy_pass http://nuxt;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;
    }
}

3. Vercel 部署

javascript 复制代码
// vercel.json - Vercel 配置
{
  "version": 2,
  "builds": [
    {
      "src": "nuxt.config.js",
      "use": "@nuxtjs/vercel-builder",
      "config": {
        "serverFiles": ["package.json"]
      }
    }
  ],
  "routes": [
    {
      "src": "/sw.js",
      "continue": true,
      "headers": {
        "Cache-Control": "public, max-age=0, must-revalidate",
        "Service-Worker-Allowed": "/"
      }
    }
  ]
}

// nuxt.config.js - Vercel 优化
export default {
  // Vercel 环境变量
  publicRuntimeConfig: {
    baseURL: process.env.VERCEL_URL 
      ? `https://${process.env.VERCEL_URL}` 
      : 'http://localhost:3000'
  },
  
  // 构建优化
  build: {
    // 分析包大小
    analyze: process.env.ANALYZE === 'true',
    
    // 提取CSS
    extractCSS: true,
    
    // 优化
    optimization: {
      splitChunks: {
        chunks: 'all',
        cacheGroups: {
          vendor: {
            test: /[\\/]node_modules[\\/]/,
            name: 'vendors',
            chunks: 'all'
          }
        }
      }
    }
  }
}

4. 服务器优化配置

环境变量管理
javascript 复制代码
// .env.production
NODE_ENV=production
BASE_URL=https://example.com
API_URL=https://api.example.com
NUXT_SECRET_KEY=your-secret-key

# 数据库配置
DB_HOST=localhost
DB_PORT=5432
DB_NAME=production_db
DB_USER=db_user
DB_PASS=secure_password

// nuxt.config.js - 环境变量配置
export default {
  // 公开运行时配置
  publicRuntimeConfig: {
    baseURL: process.env.BASE_URL || 'http://localhost:3000',
    apiURL: process.env.API_URL || 'http://localhost:8000'
  },
  
  // 私有运行时配置
  privateRuntimeConfig: {
    secretKey: process.env.NUXT_SECRET_KEY,
    dbConfig: {
      host: process.env.DB_HOST,
      port: process.env.DB_PORT,
      database: process.env.DB_NAME,
      username: process.env.DB_USER,
      password: process.env.DB_PASS
    }
  }
}
健康检查和监控
javascript 复制代码
// server/api/health.js - 健康检查端点
export default function (req, res) {
  const health = {
    status: 'OK',
    timestamp: new Date().toISOString(),
    uptime: process.uptime(),
    memory: process.memoryUsage(),
    version: process.env.npm_package_version
  }
  
  res.statusCode = 200
  res.setHeader('Content-Type', 'application/json')
  res.end(JSON.stringify(health))
}

// plugins/monitoring.js - 错误监控
export default ({ $axios, $sentry }) => {
  // API 错误监控
  $axios.onError(error => {
    if (process.client) {
      $sentry.captureException(error)
    }
    
    console.error('API Error:', error)
  })
  
  // 全局错误处理
  if (process.client) {
    window.addEventListener('error', event => {
      $sentry.captureException(event.error)
    })
    
    window.addEventListener('unhandledrejection', event => {
      $sentry.captureException(event.reason)
    })
  }
}

5. 部署自动化

CI/CD 流水线
yaml 复制代码
# .github/workflows/deploy.yml
name: Deploy Production

on:
  push:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v2
        with:
          node-version: '16'
          cache: 'npm'
      - run: npm ci
      - run: npm run lint
      - run: npm run test

  build-and-deploy:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      
      - name: Setup Node.js
        uses: actions/setup-node@v2
        with:
          node-version: '16'
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Build application
        run: npm run build
        env:
          NODE_ENV: production
      
      - name: Deploy to server
        uses: appleboy/ssh-action@v0.1.5
        with:
          host: ${{ secrets.HOST }}
          username: ${{ secrets.USERNAME }}
          key: ${{ secrets.SSH_KEY }}
          script: |
            cd /var/www/nuxt-app
            git pull origin main
            npm ci --production
            npm run build
            pm2 reload ecosystem.config.js
蓝绿部署
bash 复制代码
#!/bin/bash
# deploy.sh - 蓝绿部署脚本

APP_NAME="nuxt-app"
CURRENT_ENV=$(pm2 list | grep $APP_NAME | grep online | head -1 | awk '{print $4}')

if [ "$CURRENT_ENV" = "blue" ]; then
    NEW_ENV="green"
    NEW_PORT=3001
else
    NEW_ENV="blue"
    NEW_PORT=3000
fi

echo "部署到 $NEW_ENV 环境 (端口: $NEW_PORT)"

# 构建新版本
npm run build

# 启动新环境
PORT=$NEW_PORT NODE_ENV=production pm2 start ecosystem.config.js --name "$APP_NAME-$NEW_ENV"

# 健康检查
sleep 10
if curl -f http://localhost:$NEW_PORT/api/health; then
    echo "新环境健康检查通过"
    
    # 更新 Nginx 配置
    sed -i "s/server nuxt-app:.*/server nuxt-app:$NEW_PORT;/" /etc/nginx/nginx.conf
    nginx -s reload
    
    # 停止旧环境
    pm2 delete "$APP_NAME-$CURRENT_ENV" 2>/dev/null || true
    
    echo "部署完成"
else
    echo "新环境健康检查失败,回滚"
    pm2 delete "$APP_NAME-$NEW_ENV"
    exit 1
fi

性能优化

构建优化策略
graph TD A[性能优化] --> B[构建优化] A --> C[运行时优化] A --> D[资源优化] B --> E[代码分割] B --> F[Tree Shaking] B --> G[Bundle分析] C --> H[缓存策略] C --> I[懒加载] C --> J[预取预加载] D --> K[图片优化] D --> L[字体优化] D --> M[CSS优化]
1. 代码分割和懒加载
javascript 复制代码
// nuxt.config.js - 代码分割配置
export default {
  build: {
    // 分析bundle大小
    analyze: process.env.ANALYZE === 'true',
    
    // 优化配置
    optimization: {
      splitChunks: {
        layouts: true,
        pages: true,
        commons: true,
        cacheGroups: {
          vendor: {
            test: /[\\/]node_modules[\\/]/,
            name: 'vendors',
            chunks: 'all',
            minSize: 30000
          },
          common: {
            name: 'common',
            minChunks: 2,
            chunks: 'all',
            enforce: true
          }
        }
      }
    },
    
    // Babel 配置优化
    babel: {
      compact: true,
      presets({ isServer }) {
        return [
          [
            '@nuxt/babel-preset-app',
            {
              corejs: { version: 3 },
              targets: isServer 
                ? { node: '16' } 
                : { browsers: ['> 1%', 'last 2 versions'] }
            }
          ]
        ]
      }
    }
  }
}
2. 资源优化
javascript 复制代码
// 图片优化配置
export default {
  modules: [
    '@nuxt/image'
  ],
  
  image: {
    // 图片格式优化
    formats: ['webp', 'avif'],
    
    // 响应式图片
    screens: {
      xs: 320,
      sm: 640,
      md: 768,
      lg: 1024,
      xl: 1280
    },
    
    // 图片质量
    quality: 80,
    
    // 图片提供商
    providers: {
      cloudinary: {
        baseURL: 'https://res.cloudinary.com/your-cloud/image/fetch/'
      }
    }
  },
  
  // CSS 优化
  css: [
    // 关键CSS内联
    { src: '~/assets/css/critical.css', inline: true },
    // 非关键CSS异步加载
    '~/assets/css/main.css'
  ]
}

// components/OptimizedImage.vue - 优化的图片组件
<template>
  <NuxtImg
    :src="src"
    :alt="alt"
    :sizes="sizes"
    :loading="loading"
    :placeholder="placeholder"
    format="webp"
    quality="80"
    @load="onLoad"
  />
</template>

<script>
export default {
  props: {
    src: String,
    alt: String,
    sizes: { type: String, default: '100vw' },
    loading: { type: String, default: 'lazy' },
    placeholder: { type: Boolean, default: true }
  },
  
  methods: {
    onLoad() {
      this.$emit('load')
    }
  }
}
</script>
3. 缓存策略
javascript 复制代码
// nuxt.config.js - 缓存配置
export default {
  // 页面级缓存
  render: {
    // 静态资源缓存
    static: {
      maxAge: 1000 * 60 * 60 * 24 * 7 // 7天
    },
    
    // 压缩配置
    compressor: {
      threshold: 0
    }
  },
  
  // HTTP/2 Push
  http2: {
    push: true,
    pushAssets: (req, res, publicPath, preloadFiles) => {
      // 推送关键资源
      return preloadFiles
        .filter(f => f.asType === 'script' && f.file === 'runtime.js')
        .map(f => `<${publicPath}${f.file}>; rel=preload; as=${f.asType}`)
    }
  }
}

// 服务端缓存中间件
// middleware/cache.js
import LRU from 'lru-cache'

const cache = new LRU({
  max: 1000,
  ttl: 1000 * 60 * 15 // 15分钟
})

export default function (req, res, next) {
  const key = req.url
  
  // 检查缓存
  if (cache.has(key)) {
    const cached = cache.get(key)
    res.setHeader('X-Cache', 'HIT')
    return res.end(cached)
  }
  
  // 拦截响应
  const originalEnd = res.end
  res.end = function(chunk, encoding) {
    if (res.statusCode === 200) {
      cache.set(key, chunk)
      res.setHeader('X-Cache', 'MISS')
    }
    originalEnd.call(res, chunk, encoding)
  }
  
  next()
}
4. 预取和预加载
javascript 复制代码
// plugins/preload.client.js - 智能预加载
export default function ({ app, router }) {
  // 路由预取
  router.beforeEach((to, from, next) => {
    // 预取下一个可能的路由
    const predictedRoutes = predictNextRoutes(to.path)
    predictedRoutes.forEach(route => {
      router.resolve(route).route.matched.forEach(m => {
        if (m.components.default && typeof m.components.default === 'function') {
          m.components.default()
        }
      })
    })
    next()
  })
  
  // 图片预加载
  const preloadImages = () => {
    const images = document.querySelectorAll('img[data-preload]')
    images.forEach(img => {
      const link = document.createElement('link')
      link.rel = 'preload'
      link.as = 'image'
      link.href = img.dataset.preload
      document.head.appendChild(link)
    })
  }
  
  // 页面加载完成后预加载
  window.addEventListener('load', preloadImages)
}

function predictNextRoutes(currentPath) {
  // 基于用户行为预测下一个路由
  const predictions = {
    '/': ['/blog', '/about'],
    '/blog': ['/blog/[slug]'],
    '/user/[id]': ['/user/[id]/profile', '/user/[id]/settings']
  }
  
  return predictions[currentPath] || []
}
5. 运行时性能监控
javascript 复制代码
// plugins/performance.client.js - 性能监控
export default function ({ $gtag }) {
  // Web Vitals 监控
  import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
    getCLS(sendToAnalytics)
    getFID(sendToAnalytics)
    getFCP(sendToAnalytics)
    getLCP(sendToAnalytics)
    getTTFB(sendToAnalytics)
  })
  
  function sendToAnalytics({ name, value, id }) {
    $gtag('event', name, {
      event_category: 'Web Vitals',
      event_label: id, 
      value: Math.round(name === 'CLS' ? value * 1000 : value),
      non_interaction: true
    })
  }
  
  // 页面加载时间监控
  window.addEventListener('load', () => {
    const navigation = performance.getEntriesByType('navigation')[0]
    const loadTime = navigation.loadEventEnd - navigation.fetchStart
    
    $gtag('event', 'page_load_time', {
      event_category: 'Performance',
      value: Math.round(loadTime),
      non_interaction: true
    })
  })
}
6. PWA 优化
javascript 复制代码
// nuxt.config.js - PWA 配置
export default {
  modules: [
    '@nuxtjs/pwa'
  ],
  
  pwa: {
    // 离线缓存策略
    workbox: {
      runtimeCaching: [
        {
          urlPattern: 'https://api\\.example\\.com/.*',
          handler: 'NetworkFirst',
          options: {
            cacheName: 'api-cache',
            cacheableResponse: {
              statuses: [0, 200]
            },
            networkTimeoutSeconds: 3
          }
        },
        {
          urlPattern: '.*\\.(png|jpg|jpeg|svg|gif)$',
          handler: 'CacheFirst',
          options: {
            cacheName: 'image-cache',
            expiration: {
              maxEntries: 100,
              maxAgeSeconds: 30 * 24 * 60 * 60 // 30天
            }
          }
        }
      ]
    },
    
    // 清单文件
    manifest: {
      name: 'Nuxt.js 应用',
      short_name: 'NuxtApp',
      description: 'Nuxt.js PWA 应用',
      theme_color: '#2196F3',
      background_color: '#ffffff'
    }
  }
}
7. 内存和性能优化
javascript 复制代码
// utils/performance.js - 性能优化工具
export class PerformanceOptimizer {
  constructor() {
    this.observers = new Map()
    this.rafId = null
  }
  
  // 防抖函数
  debounce(func, wait) {
    let timeout
    return function executedFunction(...args) {
      const later = () => {
        clearTimeout(timeout)
        func(...args)
      }
      clearTimeout(timeout)
      timeout = setTimeout(later, wait)
    }
  }
  
  // 节流函数
  throttle(func, limit) {
    let inThrottle
    return function(...args) {
      if (!inThrottle) {
        func.apply(this, args)
        inThrottle = true
        setTimeout(() => inThrottle = false, limit)
      }
    }
  }
  
  // Intersection Observer 懒加载
  lazyLoad(elements, callback) {
    const observer = new IntersectionObserver((entries) => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          callback(entry.target)
          observer.unobserve(entry.target)
        }
      })
    }, {
      rootMargin: '50px 0px',
      threshold: 0.1
    })
    
    elements.forEach(el => observer.observe(el))
    this.observers.set('lazy', observer)
  }
  
  // RequestAnimationFrame 优化
  scheduleUpdate(callback) {
    if (this.rafId) {
      cancelAnimationFrame(this.rafId)
    }
    
    this.rafId = requestAnimationFrame(() => {
      callback()
      this.rafId = null
    })
  }
  
  // 清理资源
  cleanup() {
    this.observers.forEach(observer => observer.disconnect())
    this.observers.clear()
    
    if (this.rafId) {
      cancelAnimationFrame(this.rafId)
    }
  }
}

// 使用示例
export default {
  mounted() {
    this.optimizer = new PerformanceOptimizer()
    
    // 懒加载图片
    const images = this.$el.querySelectorAll('img[data-lazy]')
    this.optimizer.lazyLoad(images, (img) => {
      img.src = img.dataset.lazy
      img.removeAttribute('data-lazy')
    })
  },
  
  beforeDestroy() {
    if (this.optimizer) {
      this.optimizer.cleanup()
    }
  }
}

模块系统和扩展

Nuxt 模块生态

graph TD A[Nuxt 核心] --> B[官方模块] A --> C[社区模块] A --> D[自定义模块] B --> E["@nuxtjs/axios"] B --> F["@nuxtjs/pwa"] B --> G["@nuxtjs/content"] C --> H["@nuxtjs/tailwindcss"] C --> I["@nuxtjs/storybook"] C --> J["@nuxtjs/google-analytics"] D --> K[业务模块] D --> L[工具模块]

1. 常用官方模块

@nuxtjs/axios - HTTP 客户端
javascript 复制代码
// nuxt.config.js
export default {
  modules: [
    '@nuxtjs/axios'
  ],
  
  axios: {
    baseURL: 'https://api.example.com',
    retry: { retries: 3 },
    
    // 请求拦截器
    interceptors: {
      request: [
        (config) => {
          config.headers.common['Authorization'] = `Bearer ${getToken()}`
          return config
        }
      ],
      
      response: [
        (response) => {
          return response.data
        },
        (error) => {
          if (error.response?.status === 401) {
            // 重定向到登录页
            return redirect('/login')
          }
          return Promise.reject(error)
        }
      ]
    }
  }
}

// 在组件中使用
export default {
  async asyncData({ $axios }) {
    const posts = await $axios.$get('/posts')
    return { posts }
  },
  
  methods: {
    async createPost() {
      try {
        const post = await this.$axios.$post('/posts', this.postData)
        this.$router.push(`/posts/${post.id}`)
      } catch (error) {
        this.$toast.error('创建失败')
      }
    }
  }
}
@nuxtjs/content - 内容管理
javascript 复制代码
// nuxt.config.js
export default {
  modules: [
    '@nuxt/content'
  ],
  
  content: {
    // Markdown 配置
    markdown: {
      prism: {
        theme: 'prism-themes/themes/prism-material-oceanic.css'
      }
    },
    
    // 实时编辑
    liveEdit: false
  }
}

// pages/blog/_slug.vue - 内容页面
<template>
  <div>
    <h1>{{ article.title }}</h1>
    <p>{{ article.description }}</p>
    <nuxt-content :document="article" />
  </div>
</template>

<script>
export default {
  async asyncData({ $content, params }) {
    const article = await $content('articles', params.slug).fetch()
    
    return {
      article
    }
  },
  
  head() {
    return {
      title: this.article.title,
      meta: [
        {
          hid: 'description',
          name: 'description',
          content: this.article.description
        }
      ]
    }
  }
}
</script>

// content/articles/hello-world.md
---
title: Hello World
description: 我的第一篇文章
img: /images/hello-world.jpg
alt: Hello World
author:
  name: 张三
  bio: 前端开发工程师
tags:
  - nuxtjs
  - markdown
---

## 介绍

这是我的第一篇文章,使用 Nuxt Content 编写。

```javascript
console.log('Hello World')
css 复制代码
#### @nuxtjs/pwa - 渐进式 Web 应用

```javascript
// nuxt.config.js
export default {
  modules: [
    '@nuxtjs/pwa'
  ],
  
  pwa: {
    meta: {
      title: '我的PWA应用',
      author: '张三'
    },
    
    manifest: {
      name: '我的PWA应用',
      short_name: 'MyPWA',
      description: '基于 Nuxt.js 的 PWA 应用',
      theme_color: '#2196F3',
      background_color: '#ffffff',
      display: 'standalone',
      orientation: 'portrait',
      start_url: '/',
      icons: [
        {
          src: '/icon-192x192.png',
          sizes: '192x192',
          type: 'image/png'
        }
      ]
    },
    
    workbox: {
      // 缓存策略
      runtimeCaching: [
        {
          urlPattern: 'https://fonts\\.googleapis\\.com/.*',
          handler: 'CacheFirst',
          options: {
            cacheName: 'google-fonts-cache',
            expiration: {
              maxEntries: 30,
              maxAgeSeconds: 60 * 60 * 24 * 30
            }
          }
        }
      ]
    }
  }
}

2. 自定义模块开发

基础模块结构
javascript 复制代码
// modules/my-module/index.js
const { resolve } = require('path')

module.exports = function MyModule(moduleOptions) {
  const options = {
    ...this.options.myModule,
    ...moduleOptions
  }
  
  // 添加插件
  this.addPlugin({
    src: resolve(__dirname, 'plugin.js'),
    fileName: 'my-module.js',
    options
  })
  
  // 添加中间件
  this.addServerMiddleware({
    path: '/api/my-module',
    handler: resolve(__dirname, 'middleware.js')
  })
  
  // 扩展路由
  this.extendRoutes((routes, resolve) => {
    routes.push({
      name: 'my-module',
      path: '/my-module',
      component: resolve(__dirname, 'pages/index.vue')
    })
  })
  
  // 添加构建插件
  this.options.build.plugins.push({
    apply(compiler) {
      // Webpack 插件逻辑
    }
  })
}

// 模块元信息
module.exports.meta = require('./package.json')
插件文件
javascript 复制代码
// modules/my-module/plugin.js
import MyModuleService from './service'

export default function ({ $axios, app }, inject) {
  // 创建服务实例
  const myModuleService = new MyModuleService($axios, <%= JSON.stringify(options) %>)
  
  // 注入到 Vue 实例
  inject('myModule', myModuleService)
  
  // 添加到 Nuxt 上下文
  app.$myModule = myModuleService
}

// modules/my-module/service.js
export default class MyModuleService {
  constructor(axios, options) {
    this.$axios = axios
    this.options = options
  }
  
  async getData() {
    try {
      const { data } = await this.$axios.get('/api/data')
      return data
    } catch (error) {
      console.error('MyModule Error:', error)
      throw error
    }
  }
  
  processData(data) {
    return data.map(item => ({
      ...item,
      processed: true
    }))
  }
}

3. 插件系统

全局插件
javascript 复制代码
// plugins/global-components.js
import Vue from 'vue'
import BaseButton from '~/components/BaseButton.vue'
import BaseInput from '~/components/BaseInput.vue'
import BaseModal from '~/components/BaseModal.vue'

// 全局注册组件
Vue.component('BaseButton', BaseButton)
Vue.component('BaseInput', BaseInput)
Vue.component('BaseModal', BaseModal)

// plugins/filters.js
import Vue from 'vue'

// 全局过滤器
Vue.filter('currency', (value) => {
  if (!value) return ''
  return new Intl.NumberFormat('zh-CN', {
    style: 'currency',
    currency: 'CNY'
  }).format(value)
})

Vue.filter('dateFormat', (value, format = 'YYYY-MM-DD') => {
  if (!value) return ''
  return dayjs(value).format(format)
})
客户端专用插件
javascript 复制代码
// plugins/client-only.client.js
export default ({ app }) => {
  // 只在客户端执行
  if (process.client) {
    // 加载第三方库
    import('aos').then(AOS => {
      AOS.init({
        duration: 1000,
        once: true
      })
    })
    
    // 全局错误处理
    window.addEventListener('error', (event) => {
      console.error('Client Error:', event.error)
      // 发送错误报告
      app.$sentry.captureException(event.error)
    })
  }
}

4. 扩展 Webpack 配置

javascript 复制代码
// nuxt.config.js
export default {
  build: {
    extend(config, { isDev, isClient, isServer }) {
      // 添加 loader
      config.module.rules.push({
        test: /\.(glsl|vs|fs)$/,
        loader: 'raw-loader'
      })
      
      // 添加别名
      config.resolve.alias['@assets'] = path.resolve(__dirname, 'assets')
      config.resolve.alias['@utils'] = path.resolve(__dirname, 'utils')
      
      // 开发环境配置
      if (isDev && isClient) {
        config.module.rules.push({
          enforce: 'pre',
          test: /\.(js|vue)$/,
          loader: 'eslint-loader',
          exclude: /(node_modules)/
        })
      }
      
      // 生产环境优化
      if (!isDev) {
        // 压缩图片
        config.module.rules.push({
          test: /\.(png|jpe?g|gif|svg)$/,
          use: {
            loader: 'imagemin-webpack-loader',
            options: {
              mozjpeg: { quality: 80 },
              pngquant: { quality: [0.65, 0.8] }
            }
          }
        })
      }
    },
    
    // 添加 Webpack 插件
    plugins: [
      new webpack.DefinePlugin({
        'process.env.BUILD_TIME': JSON.stringify(new Date().toISOString())
      })
    ]
  }
}

5. TypeScript 支持

javascript 复制代码
// nuxt.config.js
export default {
  modules: [
    '@nuxt/typescript-build'
  ],
  
  typescript: {
    typeCheck: {
      eslint: {
        files: './**/*.{ts,js,vue}'
      }
    }
  }
}

// types/index.ts - 类型定义
export interface User {
  id: number
  name: string
  email: string
  avatar?: string
}

export interface Post {
  id: number
  title: string
  content: string
  author: User
  createdAt: string
  updatedAt: string
}

// pages/blog/_slug.vue - TypeScript 组件
<script lang="ts">
import { Component, Vue } from 'nuxt-property-decorator'
import { Post } from '~/types'

@Component({
  async asyncData({ $content, params }) {
    const post: Post = await $content('posts', params.slug).fetch()
    return { post }
  }
})
export default class BlogPost extends Vue {
  post!: Post
  
  get formattedDate(): string {
    return new Date(this.post.createdAt).toLocaleDateString('zh-CN')
  }
  
  head() {
    return {
      title: this.post.title,
      meta: [
        {
          hid: 'description',
          name: 'description',
          content: this.post.content.substring(0, 160)
        }
      ]
    }
  }
}
</script>

6. 测试配置

javascript 复制代码
// nuxt.config.js
export default {
  modules: [
    '@nuxtjs/jest'
  ],
  
  jest: {
    collectCoverage: true,
    coverageDirectory: '<rootDir>/coverage',
    collectCoverageFrom: [
      '<rootDir>/components/**/*.vue',
      '<rootDir>/pages/**/*.vue',
      '<rootDir>/plugins/**/*.js',
      '<rootDir>/store/**/*.js'
    ]
  }
}

// tests/components/BaseButton.spec.js
import { mount } from '@vue/test-utils'
import BaseButton from '@/components/BaseButton.vue'

describe('BaseButton', () => {
  test('renders correctly', () => {
    const wrapper = mount(BaseButton, {
      slots: {
        default: 'Click me'
      }
    })
    
    expect(wrapper.text()).toBe('Click me')
    expect(wrapper.classes()).toContain('btn')
  })
  
  test('emits click event', async () => {
    const wrapper = mount(BaseButton)
    
    await wrapper.trigger('click')
    
    expect(wrapper.emitted().click).toBeTruthy()
  })
})
相关推荐
培根芝士12 分钟前
使用 Canvas 替代 <video> 标签加载并渲染视频
前端·javascript·音视频
小螺号dididi吹43 分钟前
菜鸟速通:React入门 01
前端·react.js·前端框架
Lstmxx1 小时前
解放前端生产力:我如何用 LLM 和 Bun.js 构建一个 YApi to TypeScript 的自动化代码生成服务
前端·ai编程·mcp
持续前行1 小时前
vue3 : 导出pdf , 除iframe之外 ,还有其他内容一并导出方式
前端·javascript·vue.js
唐某人丶1 小时前
前端仔如何在公司搭建 AI Review 系统
前端·人工智能·aigc
RainbowSea1 小时前
伙伴匹配系统(移动端 H5 网站(APP 风格)基于Spring Boot 后端 + Vue3 - 02
java·vue.js·spring boot
没有鸡汤吃不下饭1 小时前
排查vue项目线上才会出现的故障
前端·vue.js·nginx
吃饭睡觉打豆豆嘛1 小时前
React Router 传参三板斧:新手也能 5 秒做决定
前端
裘乡1 小时前
storybook配合vite + react生成组件文档
前端·react.js
Carolinemy1 小时前
ElementUI 之 el-table
前端·vue.js