diff --git a/.vitepress/config.ts b/.vitepress/config.ts
index e67625c..34ea918 100644
--- a/.vitepress/config.ts
+++ b/.vitepress/config.ts
@@ -1,4 +1,11 @@
+import { createWriteStream } from 'node:fs'
 import { defineConfig } from 'vitepress'
+import { SitemapStream } from 'sitemap'
+import { resolve } from 'node:path'
+
+const links: string[] = []
+
+const hostname = 'https://blog.vuejs.org/'
 
 export default defineConfig({
   title: 'The Vue Point',
@@ -30,5 +37,19 @@ export default defineConfig({
         defer: ''
       }
     ]
-  ]
+  ],
+  transformHtml: (_, id, { pageData }) => {
+    if (!/[\\/]404\.html$/.test(id))
+      links.push(pageData.relativePath.replace(/\.md$/, '.html'))
+  },
+  buildEnd: async ({ outDir }) => {
+    const sitemap = new SitemapStream({
+      hostname
+    })
+    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))
+  }
 })
diff --git a/package.json b/package.json
index 6b3781d..f2121f2 100644
--- a/package.json
+++ b/package.json
@@ -14,6 +14,7 @@
     "@types/node": "^18.7.13",
     "feed": "^4.2.1",
     "gray-matter": "^4.0.2",
+    "sitemap": "^7.1.1",
     "tailwindcss": "^3.1.8",
     "tsx": "^3.8.2",
     "vitepress": "^1.0.0-alpha.45",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 67b28ce..d327ed8 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -6,6 +6,7 @@ specifiers:
   '@types/node': ^18.7.13
   feed: ^4.2.1
   gray-matter: ^4.0.2
+  sitemap: ^7.1.1
   tailwindcss: ^3.1.8
   tsx: ^3.8.2
   vitepress: ^1.0.0-alpha.45
@@ -17,6 +18,7 @@ devDependencies:
   '@types/node': 18.7.13
   feed: 4.2.2
   gray-matter: 4.0.3
+  sitemap: 7.1.1
   tailwindcss: 3.1.8
   tsx: 3.8.2
   vitepress: 1.0.0-alpha.45_@types+node@18.7.13
@@ -474,10 +476,20 @@ packages:
     resolution: {integrity: sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==}
     dev: true
 
+  /@types/node/17.0.45:
+    resolution: {integrity: sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==}
+    dev: true
+
   /@types/node/18.7.13:
     resolution: {integrity: sha512-46yIhxSe5xEaJZXWdIBP7GU4HDTG8/eo0qd9atdiL+lFpA03y8KS+lkTN834TWJj5767GbWv4n/P6efyTFt1Dw==}
     dev: true
 
+  /@types/sax/1.2.4:
+    resolution: {integrity: sha512-pSAff4IAxJjfAXUG6tFkO7dsSbTmf8CtUpfhhZ5VhkRpC4628tJhh3+V6H1E+/Gs9piSzYKT5yzHO5M4GG9jkw==}
+    dependencies:
+      '@types/node': 18.7.13
+    dev: true
+
   /@types/web-bluetooth/0.0.16:
     resolution: {integrity: sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==}
     dev: true
@@ -1355,6 +1367,17 @@ packages:
       vscode-textmate: 8.0.0
     dev: true
 
+  /sitemap/7.1.1:
+    resolution: {integrity: sha512-mK3aFtjz4VdJN0igpIJrinf3EO8U8mxOPsTBzSsy06UtjZQJ3YY3o3Xa7zSc5nMqcMrRwlChHZ18Kxg0caiPBg==}
+    engines: {node: '>=12.0.0', npm: '>=5.6.0'}
+    hasBin: true
+    dependencies:
+      '@types/node': 17.0.45
+      '@types/sax': 1.2.4
+      arg: 5.0.2
+      sax: 1.2.4
+    dev: true
+
   /source-map-js/1.0.2:
     resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
     engines: {node: '>=0.10.0'}