vuejs / vitepress

Vite & Vue powered static site generator.
https://vitepress.dev
MIT License
11.48k stars 1.86k forks source link

Sitemap generation #520

Closed JanWerder closed 9 months ago

JanWerder commented 2 years ago

Is your feature request related to a problem? Please describe.

vitepress does not generate a sitemap.xml

Describe the solution you'd like

vitepress should generate a sitemap based on the config.js

Describe alternatives you've considered

The user could create a sitemap by hand

Additional context

No response

Validations

aboutsimon commented 2 years ago

Like you hinted at, in the alternatives, it's fairly straightforward to build out your own sitemap generation. So just for reference, an implementation idea that might work for you, using sitemap.. This assumes you have srcDir: './pages' set.

const fs = require('fs')
const path = require('path')
const fg = require('fast-glob')
const { SitemapStream, streamToPromise } = require('sitemap')

console.log('Start building sitemap..')

const linksStream = fg.stream(['./pages/*.md'])
  .map((filePath) => ({ url: filePath.replace('pages/', '').replace(/\.md$/, '.html') }))

const sitemapStream = new SitemapStream({ hostname: 'https://www.example.org' })

// Return a promise that resolves with your XML string
streamToPromise(linksStream.pipe(sitemapStream)).then((sitemap) => {
  fs.writeFileSync(
    path.resolve(__dirname, '../pages/public/sitemap.xml'),
    sitemap,
  )
})

Note that this uses Stream#readable.map, which is a brand new node feature, but you also implement it differently, if you don't want to rely on a experimental api. It's also possible to use something like gray-matter, to include your meta data in your sitemaps, if needed.

jbaubree commented 2 years ago

If it can help, i have done a sitemap.xml and robots.txt generator using typescript: sitemap-ts. This plugin was created and externalized from vite-ssg-sitemap ans is also used in vite-plugin-sitemap.

toniengelhardt commented 2 years ago

@jbaubree amazing, I'll check it out

lmtr0 commented 1 year ago

any status on this?

jbaubree commented 1 year ago

@lmtr0 We need to wait next release of Vitepress with this pushed https://github.com/vuejs/vitepress/pull/709. We will be able to configure like that :

// .vitepress/config.ts
import { version } from '../../../../packages/snap-design/package.json'
import { generateSitemap as sitemap } from 'sitemap-ts'

export default {
  vite: {
    buildEnd: () => {
      sitemap()
    },
  },
}
lmtr0 commented 1 year ago

understood, for now a simple script solves my problem, thanks

brc-dd commented 1 year ago

On newer versions of VitePress this can be done (there is no need to traverse the dist directory):

import { createWriteStream } from 'node:fs'
import { resolve } from 'node:path'
import { SitemapStream } from 'sitemap'
import { defineConfig } from 'vitepress'

const links = []

export default {
  // ...

  transformHtml: (_, id, { pageData }) => {
    if (!/[\\/]404\.html$/.test(id))
      links.push({
        // you might need to change this if not using clean urls mode
        url: pageData.relativePath.replace(/((^|\/)index)?\.md$/, '$2'),
        lastmod: pageData.lastUpdated
      })
  },

  buildEnd: ({ outDir }) => {
    const sitemap = new SitemapStream({ hostname: 'https://vitepress.vuejs.org/' })
    const writeStream = createWriteStream(resolve(outDir, 'sitemap.xml'))
    sitemap.pipe(writeStream)
    links.forEach((link) => sitemap.write(link))
    sitemap.end()
  }
}
jk2K commented 1 year ago

if not using clean urls mode, you can use this transformHtml

transformHtml: (_, id, { pageData }) => {
    if (!/[\\/]404\.html$/.test(id))
      links.push({
        url: pageData.relativePath.replace(/\.md$/, '.html'),
        lastmod: pageData.lastUpdated
      })
  },
mil7 commented 1 year ago

Since release of 1.0.0-alpha.23 the script above does not create a file anymore even though the script runs through as far as I can judge.

Could it be that the buildEnd hook behaves differently than before? The bugfix "build: explicitly exit process after build to prevent hangup" sounds suspicious.

brc-dd commented 1 year ago

Ah right, it is exiting the process before stream can finish. Try this for now:

  buildEnd: async ({ outDir }) => {
    const sitemap = new SitemapStream({
      hostname: 'https://vitepress.vuejs.org/'
    })
    const writeStream = createWriteStream(resolve(outDir, 'sitemap.xml'))
    sitemap.pipe(writeStream)
    links.forEach((link) => sitemap.write(link))
    sitemap.end()
    await new Promise((r) => writeStream.on('finish', r))
  }
MarkusKeck commented 1 year ago

@brc-dd Maybe it would be useful if you can specify in the config file if a sitemap should be generated automatically or not.

erbill commented 1 year ago

@brc-dd Maybe it would be useful if you can specify in the config file if a sitemap should be generated automatically or not.

any updates on this?

now I'm at "vitepress": "^1.0.0-alpha.27", "vue": "^3.2.41"

    Which way to make sitemaps is the most beginner friendly method?
yangwao commented 11 months ago

Which way to make sitemaps is the most beginner friendly method?

Someone might find it usefull as people want to come and grab working snippet. I'm on "vitepress": "1.0.0-alpha.75" and seems it generates good sitemap.xml

ht for @brc-dd updates!

import { createWriteStream } from 'node:fs'
import { resolve } from 'node:path'
import { SitemapStream } from 'sitemap'
import { defineConfig } from 'vitepress'

const links = []

// ...
export default defineConfig({
  transformHtml: (_, id, { pageData }) => {
    if (!/[\\/]404\.html$/.test(id))
      links.push({
        // you might need to change this if not using clean urls mode
        url: pageData.relativePath.replace(/((^|\/)index)?\.md$/, '$2'),
        lastmod: pageData.lastUpdated
      })
  },
  buildEnd: async ({ outDir }) => {
    const sitemap = new SitemapStream({
      hostname: 'https://subwork.xyz/'
    })
    const writeStream = createWriteStream(resolve(outDir, 'sitemap.xml'))
    sitemap.pipe(writeStream)
    links.forEach((link) => sitemap.write(link))
    sitemap.end()
    await new Promise((r) => writeStream.on('finish', r))
  },
Red-Asuka commented 11 months ago

If you are not using clean URLs mode, you can write it like this:

  transformHtml: (_, id, { pageData }) => {
    if (!/[\\/]404\.html$/.test(id)) {
      links.push({
        url: pageData.relativePath.replace(/\/index\.md$/, '/').replace(/\.md$/, '.html'),
        lastmod: pageData.lastUpdated,
      })
    }
  },