vue-cli-plugin-sitemap generates sitemaps for your Vue web apps. You can use it on its own or integrate it in the definition of the routes used in Vue Router. Features:
- 🛣️ generate sitemaps from an array of routes
- 🔀 support dynamic routes with single or multiple parameters
- 🍱 support nested routes
- 🚧 automatically escape the URLs and enforce a (non-)trailing slash policy
- ✂️ automatically split the large sitemaps (more than 50,000 URLs) and generate the associated sitemap index
- ✨ optionally prettify the output
vue add sitemap
The plugin will add a script called sitemap
to your package.json
. No other
files will be modified.
The recommended way to provide data to the plugin is to pass it the array of routes used by Vue Router. To do this, you'll need to separate the declaration of the routes and the instantiation of the Vue Router into two different modules.
Below is a simplified example of this setup, using esm
to load ES6 modules into vue.config.js
(this is needed until #4477
is implemented). Note that this comes with a few restrictions in src/routes.js
:
- you can import other JS modules, but no
.vue
files becauseesm
won't know how to load them (you'll have to rely on dynamic imports using Node'srequire()
for thecomponent
property) - you can't use the
@
placeholder in the inclusion paths, as this is a bit of sugar syntax defined byvue-loader
to shorten paths when loading files with webpack
// vue.config.js
require = require('esm')(module);
const { routes } = require('./src/routes.js');
module.exports = {
pluginOptions: {
sitemap: {
baseURL: 'https://example.com',
routes,
}
}
}
// src/routes.js
export const routes = [
{
path: '/',
name: 'home',
component: () => import(/* webpackChunkName: "home" */ './views/Home.vue')
},
{
path: '/about',
name: 'about',
component: () => import(/* webpackChunkName: "about" */ './views/About.vue')
},
]
// src/main.js
import Vue from 'vue'
import Router from 'vue-router'
import App from './App.vue'
import { routes } from './routes.js'
Vue.use(Router);
const router = new Router({
mode: 'history',
base: process.env.BASE_URL,
routes,
});
new Vue({ router, render: h => h(App) }).$mount('#app');
You can also directly provide some handwritten URLs to the plugin:
// vue.config.js
module.exports = {
pluginOptions: {
sitemap: {
urls: [
'https://example.com/',
'https://example.com/about',
]
}
}
}
If both routes and URLs are provided, they will be merged together in a single sitemap. In the case of duplicated locations, handwritten URLs will prevail over their matching routes.
To examine the output without triggering the whole build process, run the following command to generate a sitemap in the current working directory:
npm run sitemap
When running the plugin on the command line, it will follow the options set in
vue.config.js
. If needed, you can overwrite those with some CLI options:
-p
,--pretty
: produce a human-readable output-o <dir>
,--output-dir <dir>
: specify a directory in which the sitemap will be written
Note: when calling the CLI through npm scripts, don't forget to add
--
before specifying the options to ensure that npm won't capture them, e.g.npm run sitemap -- --pretty -o dist/
.
All the global options are optional and can be omitted, except for baseURL
that must be provided for route-based sitemaps.
sitemap: {
// Only generate during production builds (default: `false`)
productionOnly: true,
// Define the output directory (default: global `outputDir`)
//
// Note: the official specification strongly recommends placing
// the sitemap at the root of the website
outputDir: '/temp/sitemap',
// If set to `true`, add a trailing slash at the end of every URL
// If set to `false`, always remove it (default: `false`)
trailingSlash: false,
// Set to `true` to produce URLs compatible with hash mode
// (default: `false`)
hashMode: false,
// Insert line breaks and tabulations to make the generated
// file more readable (default: `false`)
pretty: true,
// Define an URL which will serve as a prefix for every URL
// in the sitemap
// If it is provided, all URLs must be partial and not start with the
// domain name (e.g. '/page/subpage')
//
// Note: this is required if some routes are provided, because
// every URL in the sitemap must be a full URL that includes
// the protocol and domain
baseURL: 'https://example.com',
// Default meta tags for every URL
// These will be overridden by URL-specific tags
defaults: {
lastmod: '2020-01-01',
changefreq: 'weekly',
priority: 1.0,
},
}
In the sitemap format, each URL can be associated with some optional meta tags to help the crawlers update the pages and prioritize the critical URLs:
Meta tag | Accepted values for the equivalent property | Default value if absent |
---|---|---|
lastmod |
a date string in the W3C format, a JavaScript timestamp string, a numeric timestamp or a Date object |
Ø |
changefreq |
"always" , "hourly" , "daily" , "weekly" , "monthly" , "yearly" , "never" |
Ø |
priority |
a multiple of 0.1 between 0.0 and 1.0 |
0.5 |
For more information on those meta tags, you can consult the official specification.
Example with a route object:
{
path: '/about'
component: () => import(/* webpackChunkName: "about" */ './About')
meta: {
sitemap: {
lastmod: 'December 22, 2019',
priority: 0.8,
changefreq: 'daily',
}
}
}
Example with a handwritten URL:
sitemap: {
urls: [
{
loc: 'https://example.com/about',
lastmod: 'December 22, 2019',
priority: 0.8,
changefreq: 'daily',
},
]
}
If you use dynamic routes (e.g. /user/:id
), you must provide some slugs to
generate the corresponding URLs (or set the ignoreRoute
option to true):
// src/routes.js
module.exports = [
{
path: '/articles/:title',
meta: {
sitemap: {
slugs: [
'my-amazing-article',
'a-life-changing-method-for-folding-socks',
// Slugs can have their own meta tags
{
title: 'a-very-important-article',
priority: 1.0,
}
],
}
}
},
{
// Optional and regex-validated parameters are supported
path: '/blog/:category/:id(\\d+)/:title?',
meta: {
sitemap: {
// For dynamic routes with multiple parameters, each slug
// must be an object with a key for each parameter
slugs: [
{
id: 1,
title: 'hello-world',
category: 'infos',
},
{
id: 2,
title: 'how-to-fold-socks-faster',
category: 'lifehacks',
priority: 0.9,
lastmod: 'February 02, 2020 09:24',
},
{
// Slugs that don't match the regex pattern
// of their parameter will throw an error
id: 'invalid-slug',
title: 'another-post',
category: 'misc',
}
]
}
}
},
{
path: '/user/:id',
meta: {
sitemap: {
// Slugs can also be provided asynchronously
// The callback must always return an array
slugs: async () => await getActiveUsers(),
}
}
},
]
Nested routes are supported:
// src/routes.js
module.exports = [
{
path: '/user/:id',
meta: {
sitemap: {
// The meta properties of parents
// will be inherited by their children
changefreq: 'monthly',
priority: 0.7,
slugs: getUserList(),
}
},
children: [
{
path: 'profile',
meta: {
sitemap: {
// The meta properties of children
// override those of their parents
changefreq: 'weekly',
}
}
},
]
},
]
This example will produce the following sitemap:
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://example.com/user/1/profile</loc>
<priority>0.7</priority>
<changefreq>weekly</changefreq>
</url>
<url>
<loc>https://example.com/user/2/profile</loc>
<priority>0.7</priority>
<changefreq>weekly</changefreq>
</url>
<!-- [...] -->
</urlset>
// src/routes.js
module.exports = [
{
path: '/admin/secure-page',
// Explicitly ignore this route and all its children
meta: { sitemap: { ignoreRoute: true } }
},
{
// Routes with a glob in their path will be ignored...
path: '*',
name: '404',
},
{
path: '/glob/*',
// ...unless you provide a handwritten path to replace it
meta: { sitemap: { loc: '/glob/lorem/ipsum' } }
},
]
You can consult the full changelog here.
This software is distributed under the ISC license.