Code前端首页关于Code前端联系我们

vue-router 怎么生成 sitemap?

terry 9小时前 阅读数 13 #Vue
文章标签 router sitemap

做Vue项目时,很多人用vue-router管理前端路由,但要让网站被搜索引擎更好收录,少不了站点地图(sitemap),可vue-router是前端路由,SPA(单页应用)又有路由动态加载的特点,怎么把vue-router里的路由规则转成能被搜索引擎识别的sitemap呢?这篇文章从痛点、思路到具体操作,一步步说清楚。

SPA 用 vue-router 生成 sitemap 有啥痛点?

先明白SPA和传统多页应用的区别,传统网站每个页面是独立HTML,路由切换对应不同页面文件;但Vue的SPA是“单页”,靠前端JS动态渲染内容,初始HTML里可能只有个空容器(比如<div id="app"></div>)。

搜索引擎爬虫爬取时,若没做服务端渲染(SSR)或静态生成(SSG),拿到的HTML可能没实际内容,导致收录效果差,而sitemap是给爬虫“指路”的,得把所有重要页面的URL列清楚,但vue-router的路由是写在前端代码里的,比如routes: [ { path: '/home' }, { path: '/article/:id' } ],这些路由怎么变成sitemap里的有效URL?

还有两个关键问题:一是动态路由,像/article/:id这种带参数的路由,实际要对应/article/1/article/2等真实页面,这些ID从哪来?二是路由筛选,不是所有路由都要被收录(比如登录页、404页),怎么标记和过滤?

理清 vue-router 生成 sitemap 的核心思路

生成sitemap的本质,是把vue-router里的路由规则,转成搜索引擎能理解的URL列表(还要带更新频率、权重等信息),核心分两步:提取路由 + 处理动态参数

先看静态路由(比如/home/about):这些路由的path是写死的,只要筛选出需要收录的,直接拼接成完整URL(比如https://你的域名/home)就行。

再看动态路由(比如/article/:id/user/:name):这类路由的path带变量,得结合实际数据生成具体URL,比如博客系统里,每个文章对应一个ID,得从后端接口(或前端数据源)拿到所有文章ID,再和路由模板拼接,生成/article/1/article/2这类真实URL。

所以整体思路是:先从vue-router配置里拿到所有路由规则→过滤掉不需要收录的(比如登录页)→静态路由直接生成URL,动态路由结合数据源生成URL→把这些URL整理成sitemap.xml的格式。

手动配置静态路由生成 sitemap 的步骤

如果项目里大多是静态路由,手动配置+简单脚本就能生成sitemap,以常见的Vue CLI项目为例,步骤如下:

提取vue-router的路由配置

假设路由配置在src/router/index.js里,导出了routes数组。

import Vue from 'vue'
import Router from 'vue-router'
import Home from '@/views/Home.vue'
import About from '@/views/About.vue'
Vue.use(Router)
export default new Router({
  routes: [
    {
      path: '/home',
      name: 'Home',
      component: Home,
      meta: { index: true } // 自定义meta标记是否要收录
    },
    {
      path: '/about',
      name: 'About',
      component: About,
      meta: { index: true }
    },
    {
      path: '/login',
      name: 'Login',
      component: () => import('@/views/Login.vue'),
      meta: { index: false } // 登录页不收录
    }
  ]
})

这里用meta.index标记是否要加入sitemap,方便后续过滤。

筛选并生成基础URL列表

写个脚本(比如sitemap-generator.js),读取routes,过滤出meta.index为true的路由,然后拼接成完整URL,假设网站域名是https://example.com,代码大概这样:

const router = require('./src/router') // 引入路由配置
const baseUrl = 'https://example.com'
const sitemapUrls = []
router.routes.forEach(route => {
  if (route.meta && route.meta.index) {
    const url = baseUrl + route.path
    sitemapUrls.push({
      loc: url,
      changefreq: 'weekly', // 每周更新
      priority: 0.7 // 权重,0-1之间
    })
  }
})
console.log(sitemapUrls)

这样就得到了静态路由对应的URL列表,每个URL还带了更新频率和权重。

生成sitemap.xml文件

sitemap.xml是有固定格式的,需要用<urlset>包裹多个<url>,每个<url>里有<loc>(地址)、<lastmod>(最后修改时间)、<changefreq>(更新频率)、<priority>(权重),可以手动拼接XML,也用现成库(比如sitemap npm包)。

sitemap库的话,先安装:npm install sitemap,然后修改脚本:

const { SitemapStream, streamToPromise } = require('sitemap')
const { Readable } = require('stream')
const router = require('./src/router')
const fs = require('fs')
const path = require('path')
async function generateSitemap() {
  const baseUrl = 'https://example.com'
  const sitemapStream = new SitemapStream({ hostname: baseUrl })
  // 手动遍历路由写入
  router.routes.forEach(route => {
    if (route.meta && route.meta.index) {
      sitemapStream.write({
        url: route.path,
        changefreq: 'weekly',
        priority: 0.7
      })
    }
  })
  sitemapStream.end()
  const sitemap = await streamToPromise(sitemapStream)
  // 把生成的sitemap.xml写入public目录(Vue CLI的public目录部署后会在根路径)
  fs.writeFileSync(path.resolve(__dirname, 'public/sitemap.xml'), sitemap.toString())
}
generateSitemap()

运行这个脚本(比如在package.json里加"generate:sitemap": "node sitemap-generator.js"),就能在public目录生成sitemap.xml了。

动态路由场景下怎么生成有效URL?

动态路由(比如/article/:id)的难点是“参数从哪来”,因为路由里的:id是变量,必须结合真实数据(比如所有文章的ID)才能生成具体URL,这时候得分两步:获取数据源 + 拼接URL

确定动态路由的数据源

数据源可以是后端接口(比如/api/articles返回所有文章的ID或slug),也可以是前端自己维护的数组(比如模拟数据),举个博客项目的例子,后端有个接口https://example.com/api/articles,返回数据结构像:

[
  { "id": 1, "title": "文章1", "slug": "post-1" },
  { "id": 2, "title": "文章2", "slug": "post-2" }
]

如果路由是{ path: '/article/:id', name: 'Article', meta: { index: true } },那就要用这些id生成/article/1/article/2

结合路由规则和数据源生成URL

在生成sitemap的脚本里,调用接口获取数据,再遍历生成URL,修改之前的脚本:

const { SitemapStream, streamToPromise } = require('sitemap')
const { Readable } = require('stream')
const router = require('./src/router')
const axios = require('axios') // 要安装axios:npm install axios
const fs = require('fs')
const path = require('path')
async function generateSitemap() {
  const baseUrl = 'https://example.com'
  const sitemapStream = new SitemapStream({ hostname: baseUrl })
  // 处理静态路由(和之前一样)
  router.routes.forEach(route => {
    if (route.meta && route.meta.index && !route.path.includes(':')) { // 静态路由不含:
      sitemapStream.write({
        url: route.path,
        changefreq: 'weekly',
        priority: 0.7
      })
    }
  })
  // 处理动态路由:文章详情页
  const res = await axios.get('https://example.com/api/articles')
  const articles = res.data
  articles.forEach(article => {
    const dynamicRoute = '/article/:id' // 假设路由里的path是这个
    const url = dynamicRoute.replace(':id', article.id) // 替换成/article/1
    sitemapStream.write({
      url,
      changefreq: 'monthly', // 文章更新频率低些
      priority: 0.6
    })
  })
  sitemapStream.end()
  const sitemap = await streamToPromise(sitemapStream)
  fs.writeFileSync(path.resolve(__dirname, 'public/sitemap.xml'), sitemap.toString())
}
generateSitemap()

这样动态路由对应的真实URL就被生成了,如果有多个动态路由(比如/user/:name),同理,只要找到对应的数据源(比如用户列表接口),就能批量生成URL。

处理嵌套路由的特殊情况

如果用了嵌套路由(children),

{
  path: '/blog',
  name: 'Blog',
  component: Blog,
  meta: { index: true },
  children: [
    {
      path: 'category',
      name: 'BlogCategory',
      component: BlogCategory,
      meta: { index: true }
    }
  ]
}

嵌套路由的URL是父路由+子路由,比如/blog/category,所以遍历children时,要把父path和子path拼接,可以在脚本里递归处理routes数组,或者遍历children属性:

function handleNestedRoutes(route, parentPath = '') {
  const fullPath = parentPath + route.path
  if (route.meta && route.meta.index) {
    // 处理当前路由的URL
    sitemapStream.write({ url: fullPath, ... })
  }
  if (route.children && route.children.length > 0) {
    route.children.forEach(child => {
      handleNestedRoutes(child, fullPath + '/') // 假设父路由以/子路由不加重复/
    })
  }
}
// 初始调用:parentPath为空
router.routes.forEach(route => {
  handleNestedRoutes(route, '')
})

这样不管多少层嵌套路由,都能正确生成完整URL。

借助工具自动生成 sitemap(提升效率)

手动写脚本适合简单项目,复杂项目可以用现成工具或库,减少重复劳动。

用通用sitemap生成库结合vue-router

比如sitemap-generator这个库(npm包),它能自动爬取网站链接生成sitemap,但SPA的话,因为前端路由是JS渲染,直接爬取可能只拿到首页,所以更适合结合vue-router的路由配置,而不是爬取页面。

另一个思路是用社区工具(如vue-router-sitemap),但实际更常见的是自己结合路由配置和数据源写逻辑——因为每个项目的动态路由数据源不一样,通用工具难覆盖所有场景。

服务端渲染/静态生成框架的内置能力

如果用Nuxt.js(Vue的服务端渲染框架),生成sitemap更简单,Nuxt的路由基于pages目录,静态路由自动生成,动态路由可以用@nuxtjs/sitemap模块处理。

步骤:

  1. 安装模块:npm install @nuxtjs/sitemap
  2. nuxt.config.js里配置:
    export default {
    modules: ['@nuxtjs/sitemap'],
    sitemap: {
     hostname: 'https://example.com',
     routes: async () => {
       // 静态路由由Nuxt自动处理,动态路由需要手动补充
       const res = await axios.get('https://example.com/api/posts')
       return res.data.map(post => `/post/${post.slug}`)
     }
    }
    }

    Nuxt构建时,会自动把pages目录的路由和routes函数里的动态路由合并,生成sitemap.xml

类似的,用VitePress(Vue的静态站点生成器,适合文档站)时,路由基于docs目录的md文件,生成sitemap可以用vitepress-sitemap插件,原理也是遍历文件对应的路由,生成URL。

结合服务端渲染(SSR)或静态站点生成(SSG)优化sitemap

SPA的SEO痛点,本质是“爬虫拿到的HTML没内容”,用SSR或SSG能解决这个问题:SSR是服务端渲染HTML后返回,SSG是构建时生成每个路由的静态HTML,这两种方式下,生成sitemap更自然。

比如用Nuxt.js的SSG模式(nuxt generate),每个路由都会生成对应的HTML文件,这时候生成sitemap,不仅能拿到所有路由的URL,还能给每个URL设置准确的lastmod(比如基于文件修改时间或文章发布时间)。

再比如VuePress(静态站点生成器,用于文档),它的路由是基于docs目录的结构,生成sitemap可以用官方或社区的sitemap插件,自动遍历所有页面的路由,生成包含更新时间、权重的sitemap.xml

如果项目对SEO要求高,优先考虑用SSR(Nuxt.js)或SSG(VitePress、VuePress、Nuxt.js的generate模式),既能解决内容渲染问题,又能简化sitemap生成。

sitemap 生成后的注意事项

生成sitemap只是第一步,还要确保搜索引擎能正确识别和使用它。

合理设置路由权重(priority)

sitemap里的priority是0-1的数值,表示页面相对于站点内其他页面的重要性,一般首页设8-1,二级页面(比如分类页)设6-0.7,动态详情页(比如文章页)设5-0.6,但注意,priority只对同站点内的页面优先级有指导作用,搜索引擎不一定完全按这个来。

维护sitemap的更新机制 更新后(比如发了新文章、改了页面),sitemap要同步更新,可以在CI/CD流程里加个步骤,每次部署前运行生成sitemap的脚本,比如用GitHub Actions,当代码push到main分支时,自动运行npm run generate:sitemap,再部署。

处理动态内容的时效性(lastmod)

lastmod是页面最后修改时间,格式要符合ISO 8601(比如2024-01-01),动态页面(比如文章)的lastmod可以从数据源里拿(比如文章的updateTime字段),静态页面可以用构建时间,准确的lastmod能让搜索引擎知道“这个页面多久没更新了,需不需要重新爬取”。

提交sitemap到搜索引擎平台

生成的sitemap.xml要放在网站根目录(比如https://example.com/sitemap.xml),

  1. robots.txt里声明:Sitemap: https://example.com/sitemap.xml
  2. 到Google Search Console、百度资源平台等提交sitemap地址,让爬虫主动抓取。

实际案例参考(让方法更落地)

案例1:Vue CLI + 静态路由 + 动态路由(文章页)

假设是个博客项目,有首页、关于页(静态路由),还有文章详情页(动态路由)。

  1. 路由配置src/router/index.js):
    import Vue from 'vue'
    import Router from 'vue-router'
    import Home from '@/views/Home.vue'
    import About from '@/views/About.vue'
    import Article from '@/views/Article.vue'

Vue.use(Router)

export default new Router({ routes: [ { path: '/', name: 'Home', component: Home, meta: { index: true } }, { path: '/about', name: 'About', component: About, meta: { index: true } }, { path: '/article/:id', name: 'Article', component: Article, meta: { index: true } } ] })


2. **写生成sitemap的脚本**(`sitemap-generator.js`):  
```js
const { SitemapStream, streamToPromise } = require('sitemap')
const { Readable } = require('stream')
const router = require('./src/router')
const axios = require('axios')
const fs = require('fs')
const path = require('path')
async function generateSitemap() {
  const baseUrl = 'https://myblog.com'
  const sitemapStream = new SitemapStream({ hostname: baseUrl })
  // 处理静态路由
  router.routes.forEach(route => {
    if (route

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门