Nuxt.js 学习复盘:核心概念与实战要点

nuxt.js的优点

  • 解决SEO难题:传统VUE等SPA应用是客户端渲染(CSR),页面初始化HTML内容为空,依赖JS执行后动态生成,很难被爬虫引擎抓取;而nuxt.js默认支持服务端渲染,在服务端就生成完整的HTML内容发送给浏览器,解决了SEO的痛点;
  • 提升首屏加载速度:由于用户一请求获得的就是完整的页面,所以展现更快;
  • 灵活的渲染模式:除了服务端渲染(SSR),还支持静态站点生成(SSG),可以在构建时就生成静态HTML文件,访问速度快,适合不经常修改的页面。
  • 开发效率快:简化的路由配置、位于components下的组件无需导入就可直接使用。

打包命令

  • nuxt build: 为生产环境构建应用, 包含服务端代码;
  • nuxt generate: 静态站点生成(SSG)的核心命令,只输出纯静态文件。(如果你的项目是纯静态站点,用改命令即可。)

渲染模式

  • SSR(服务端渲染):用户请求时,服务器动态生成,适合内容频繁变化、需要实时数据的页面;
  • CSR(客户端渲染):像VUE等传统SPA一样,仅返回空的HTML等待用户打开之后再加载JS渲染,适用于不用SEO的内容;
  • SSG(静态站点生成):在构建的时候就预先生成所有静态HTML页面,适合博客等经常不变的内容;
  • 混合渲染:通过routeRules为不同路由指定不同的渲染模式,适合一个项目中既有产品详情页(SSR)和帮助中心(SSG)。
    以下是routeRules的说明:
js 复制代码
// nuxt.config.ts
export default defineNuxtConfig({
  // 全局启用 SSR
  ssr: true, 
  routeRules: {
	// 增量静态再生:当用户访问该动态页面的时候,会缓存3600秒(如果想更精细化缓存可以使用cache),如果超过这个时间,会回重新生成新页面,适用于内容定期更新的新闻列表、博客首页等,如果设置为false就是仅缓存不验证
    '/blog/**': { swr: 3600 },  
    // 管理后台走 CSR(客户端渲染),用户打开页面之后才会加载js,不具备SEO能力
    '/admin/**': { ssr: false }, 
    // 采用SSG模式(静态站点生成),即在构建阶段就生成该HTML,适用于内容几乎不变的网页
    '/': { prerender: true },   
  }
}) 

重要提醒 :在使用 nuxt generate 时,routeRules 中的 ssrswr 等选项不会生效 ,因为 nuxt generate 只生成静态文件,不启动服务端逻辑

预渲染(SSG的实现方式)

前面提到了SSG,会在构建的时候先生成所有静态的HTML页面。那如果我希望页面路由虽然是动态的(/blog/:id),但是又希望它在构建的时候可以自动生成该怎么办?

借助a标签

html 复制代码
<nuxt-link to='/blog/1'>帖子1</nuxt-link>
<nuxt-link to='/blog/2'>帖子2</nuxt-link>

nuxt.config.ts 中开启 crawlLinks: true。如果在a标签已经写好了要预渲染的帖子路径,那么在构建的时候,会自动将这两个帖子进行静态构建。

手动指定路由

提高seo的时候,可以提供一份sitemap.xml文件,一般情况下可以直接放在public文件夹下,这样打包的时候会自动复制到打包的文件夹中,但是如果sitemap.xml中的内容是动态生成的话,那就可以在预渲染的时候动态生成,然后更新其内容:

js 复制代码
export default defineNuxtConfig({
  nitro: {
    prerender: {
      routes: ['/sitemap.xml'], // 手动添加路由
    },
  },
});

当爬虫无法进入的页面(比如登录后才会显示、路由参数太复杂),但是其需要预渲染的时候,就可以使用这种方法。

路由规则

你可以利用 routeRules 进行更精细的控制,按路由模式来决定是否预渲染。

js 复制代码
export default defineNuxtConfig({
  routeRules: {
    '/': { prerender: true },        // 预渲染首页
    '/blog/**': { prerender: true }, // 预渲染 /blog 下的所有页面
    '/admin/**': { prerender: false }, // 跳过 /admin 下的页面
  },
});

动态/编程式添加

之前动态生成帖子的数量比较熟,手动配置问题不大,但是如果是数量较多的情况下呢?

  • 使用 prerenderRoutes 工具函数:
js 复制代码
<script setup>
// 在页面组件中动态添加路由,适合在运行时才知道的动态路由,仅在构建时会生成静态页面
prerenderRoutes(['/dynamic/1', '/dynamic/2']);
</script>
  • 使用 prerender:routes 钩子: 在构建的时候先请求,再生成:
js 复制代码
export default defineNuxtConfig({
  hooks: {
    async 'prerender:routes'(ctx) {
      // 从 API 获取所有博客文章ID
      const posts = await $fetch('https://api.example.com/posts');
      for (const post of posts) {
        ctx.routes.add(`/blog/${post.slug}`);
      }
    }
  }
});
  • 使用 nitro:config 钩子:
js 复制代码
export default defineNuxtConfig({
  hooks: {
    async 'nitro:config'(nitroConfig) {
      if (nitroConfig.dev) return;
      const routes = await fetchDynamicRoutes(); // 你的获取数据逻辑
      nitroConfig.prerender = nitroConfig.prerender || {};
      nitroConfig.prerender.routes = [...(nitroConfig.prerender.routes || []), ...routes];
    }
  }
});

提高SEO

用nuxt一般也是为了提高SEO,下面小结一下提高SEO的常见方式

内容与元信息优化

  • 设置全局默认标题/模板(未单独配置的页面默认使用这里的配置)
js 复制代码
// nuxt.config.ts
export default defineNuxtConfig({
  app: {
    head: {
	. // %s 会被页面具体标题替换
      titleTemplate: '%s - 我的电商网站', 
      // 没有页面标题时使用 
      defaultTitle: '网站名称',                
      meta: [
        { name: 'viewport', content: 'width=device-width, initial-scale=1' }
      ]
    }
  }
})
  • 给页面单独设置
js 复制代码
<script setup>
useHead({
  title: '商品列表 - 我的电商网站',
  meta: [
    { name: 'description', content: '全场商品限时折扣,正品保证' },
    { name: 'keywords', content: '手机, 电脑, 家电' },
  ]
})
</script>
  • 规范链接:同一商品可以通过 /product/123/product/123?ref=ad 访问,需要告诉搜索引擎当前页面的"标准"网址是哪个
js 复制代码
<script setup>
useHead({
  link: [
    { rel: 'canonical', href: 'https://example.com/current-page-path' }
  ]
})
</script>

打包与性能优化

(搜索引擎会评估页面加载速度和用户体验,所以需要进行优化。)

  • 评估之后选择渲染模式;

  • 图片优化:使用 @nuxt/image 模块,自动转换为现代格式(webp/avif)、懒加载、响应式图片。

    js 复制代码
    // nuxt.config.js
    modules: ['@nuxt/image']
    
    // 页面使用
    <NuxtImg src="/photo.jpg" format="webp" loading="lazy" />
  • 代码分割与懒加载:nuxt会自动按照路由分隔代码,也会在 components/ 目录下的每个组件生成一个懒加载版本,只需在组件名前面加上 Lazy 前缀,即可获得一个异步加载的组件、使用第三方库按需导入(比如lodash中的某些方法)

  • 预加载某些关键资源

js 复制代码
useHead({
  link: [
    { rel: 'preload', href: '/fonts/main.woff2', as: 'font', crossorigin: '' }
  ]
})
  • 进一步启用进一步启用 gzipbrotli 压缩代码(需服务器配置)。

向搜索引擎主动提交文件

  • sitemap.xml:动态生成(通过 server/routes/sitemap.xml.ts@nuxtjs/sitemap 模块)并提交到 Google Search Console / 百度资源平台。
  • robots.txt:放在 public/robots.txt,允许搜索引擎爬取。

补充

sitemap.xml

sitemap.xml 的核心作用是主动告诉搜索引擎你的网站上有哪些重要页面,以及它们的更新频率、优先级和最后修改时间 ,从而引导爬虫更高效、更完整地抓取和索引内容。

有时候sitemap.xml的内容会过多,所以会拆分成多个子xml,比如当前sitemap.xml为:

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>  
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">  
    <sitemap>  
        <loc>https://name.com/sitemap-main.xml</loc>  
    </sitemap>  
    <!-- 可以是子域名下的 -->
    <sitemap>  
        <loc>https://aa.name.com/sitemap-news.xml</loc>  
    </sitemap> 
    <sitemap>  
        <loc>https://name.com/sitemap-2026-01-15.xml</loc>  
    </sitemap>  
</sitemapindex>

这时候我会将这个网站静态的页面写在sitemap-main.xml中。

xml 复制代码
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">  
  <url>  
    <loc>https://name.com/index.html</loc>  
  </url>  
  <url>  
    <loc>https://name.com/about/index.html</loc>  
  </url>  
</urlset>

动态生成sitemap.xml

运营希望我每天可以新增一个sitemap-日期文件.xml,这样就不仅是生成新的xml文件,也要更新插入到sitemap.xml,即:

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>  
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">  
    <sitemap>  
        <loc>https://name.com/sitemap-main.xml</loc>  
    </sitemap>  
    <!-- 省略中间 -->
    <sitemap>  
        <loc>https://name.com/sitemap-2026-01-15.xml</loc>  
    </sitemap>  
</sitemapindex>

我的sitemap-日期文件.xml是一个涵盖了动态路由的文件,内容太多,不能使用ssg一次性构建,不然一天几百个页面,n天后会炸的,而且seo效果肯定也不好。另外我还需要将每天的生成的sitemap-日期文件.xml提交到搜索引擎,所以就需要写定时任务,刚好nuxt.js就可以让你写服务端的内容,在项目的server/api下新建generateSite.js

js 复制代码
export async function generateSitemapCore(customDate = null) {
  try {
    
    // 1. 这里就根据自身逻辑去编写xml的内容
    xmlContent= `...`;

    // 2. 写入sitemap-当前日期.xml文件并放到正确的位置
    const publicDir = path.resolve(process.cwd(), '.output', 'public');
    const sitemapFileFullPath = path.join(publicDir, sitemapFileName);
    const sitemapIndexPath = path.join(publicDir, 'sitemap.xml');
    // 确保目录存在
    try {
      await fs.access(publicDir);
    } catch {
      await fs.mkdir(publicDir, { recursive: true });
    }
    // 写入日期Sitemap
    await fs.writeFile(sitemapFileFullPath, xmlContent, 'utf8');
    console.log(`[定时/手动] 日期Sitemap生成成功:${sitemapFileName}`);

    // 2.更新sitemap.xml
    const newSitemapNode = `    <sitemap>\n        <loc>${newSitemapLoc}</loc>\n    </sitemap>`;
    let indexContent = '';
    try {
      indexContent = await fs.readFile(sitemapIndexPath, 'utf8');
      indexContent = indexContent.trim();
    } catch {
      indexContent = `<?xml version="1.0" encoding="UTF-8"?>\n<sitemapindex xmlns="${SITEMAP_XMLNS}">\n</sitemapindex>`;
      console.log(`[定时/手动] 首次生成Sitemap索引文件:sitemap.xml`);
    }
    if (indexContent.includes(newSitemapLoc)) {
      console.log(`[定时/手动] 索引文件已存在该节点,无需追加:${newSitemapLoc}`);
    } else {
      const closeTag = `</sitemapindex>`;
      indexContent = indexContent.replace(closeTag, `${newSitemapNode}\n${closeTag}`);
      await fs.writeFile(sitemapIndexPath, indexContent, 'utf8');
      console.log(`[定时/手动] 索引文件更新成功:已追加 ${newSitemapLoc}`);
    }
    return {
      success: true,
      msg: `Sitemap生成成功(${sitemapFileName}),索引文件已更新,URL提交已触发`,
      data: { sitemapFileName, newSitemapLoc, urlCount: generateUrlList.length }
    };

  } catch (err) {
    console.error(`[定时/手动] Sitemap生成失败:`, err.message, err.stack);
    return {
      success: false,
      msg: 'Sitemap生成失败',
      error: err.message
    };
  }
}

接下来我写定时任务,只要文件放在 server/plugins/ 目录下并正确使用 defineNitroPlugin,它就会在服务器启动时自动加载和执行:

js 复制代码
// src/server/plugins/sitemap-cron.js
import schedule from 'node-schedule';

export default defineNitroPlugin(async () => {
  // 服务启动时,导入抽离的Sitemap核心生成函数(路径和你实际文件一致)
  const { generateSitemapCore } = await import('../api/generateSite.js');

  // ======================================
  // 配置定时任务规则:每天凌晨1点执行(推荐低峰期)
  // Cron表达式:分 时 日 月 周
  // ======================================
  // 规则1:每天凌晨1点执行(生产环境推荐)
  const cronRule = '0 1 * * *';
  // 测试规则:每分钟执行一次(开发时用,验证成功后改回上面的生产规则)
  // const cronRule = '*/5 * * * *';

  // 注册定时任务
  schedule.scheduleJob(cronRule, async () => {
    console.log('==================== 定时任务开始执行:生成Sitemap并提交URL ====================');
    try {
      // 调用核心函数,不传参则自动使用「当天日期」生成(和手动接口逻辑一致)
      const result = await generateSitemapCore();
      if (result.success) {
        console.log('定时任务执行成功:', result.msg);
      } else {
        console.error('定时任务执行失败:', result.error);
      }
    } catch (err) {
      console.error('定时任务执行异常:', err.message, err.stack);
    }
    console.log('==================== 定时任务执行结束 ====================\n');
  });

  // 服务启动时打印日志,确认定时任务已注册
  console.log(`✅ Sitemap定时任务已注册成功,执行规则:${cronRule}(分 时 日 月 周)`);
});

服务器的部署问题

  • 执行npm run build之后,将整个output文件夹丢到线上服务器去;
  • 如果每次都更新了package.json则需要执行npm install --omit=dev --ignore-scripts
  • 在服务器中安装pm2
  • 在项目文件夹中执行pm2 start .output/server/index.mjs --name 自定义项目名称
  • 记得每次更新的时候执行pm2 stop 项目名称
  • 重启命令为pm2 restart 项目名称

小结

  • 了解nuxt的优点,主要是为了实现SEO;
  • 了解nuxt的渲染模式:服务端渲染(SSR)、静态渲染(SSG)、客户端渲染(CSR)以及他们的区别,通常情况下实际业务选择混合模式,不常变更的页面使用SSG(且可以搭配缓存进行使用),需要变更的页面使用SSR;
  • 了解提高SEO的常用方式:页面信息配置、页面性能/代码体积优化、主动提交给搜索引擎;
  • 学会动态生成并提交sitemap和服务端部署。
相关推荐
AI科技星2 小时前
万能学习方法论的理论建构与多领域适配性研究(乖乖数学)
人工智能·学习·算法·机器学习·平面·数据挖掘
safestar20122 小时前
React 19实战:Action、并发与性能,一次告别“意大利面状态”的升级
开发语言·javascript·vue.js
ZhaoJuFei2 小时前
React生态学习路线
前端·学习·react.js
早點睡3902 小时前
ReactNative项目OpenHarmony三方库集成实战:react-native-calendar-events(读取不到日历里新增的事件,待排查)
javascript·react native·react.js
apcipot_rain2 小时前
CSS知识概述
前端·css
837927397@QQ.COM2 小时前
个人理解无界原理
开发语言·前端·javascript
冰暮流星2 小时前
javascript之Dom查询操作1
java·前端·javascript
admin and root2 小时前
XSS之Flash弹窗钓鱼
前端·网络·安全·web安全·渗透测试·xss·src
寒秋花开曾相惜2 小时前
【软考中级系统集成项目管理】1.3 产业现代化(1.3.1 农业农村现代化)
笔记·学习