用 VitePress Rewrites 按日期整理原始 markdown
在轉移舊文章到 VitePress 部落格的過程中,遇到了一個檔案管理的挑戰,隨著部落格文章越來越多,檔案管理開始變得有點頭痛。 好幾十篇文章全部平鋪在同一個目錄下,每次想找特定時期的文章都要捲半天。 但如果把文章按日期分到子目錄,URL 又會變得很醜,重新生成 URL 也會影響 SEO。
後來發現 VitePress 的 rewrites 功能可以完美解決這個問題,這篇文章就來聊聊實作過程。
問題:檔案太多不好管理
原本的檔案結構是這樣:
docs/notes/
├── integrating-waline-comments-to-vitepress.md
├── javascript-clean-code-practices.md
├── vue3-composition-api-patterns.md
├── ecmascript-5-strict-mode.md
├── ... (略)
└── index.md遇到的問題:
- 檔案列表太長:IDE 的檔案樹要滾很久才能找到想要的檔案
- 不知道文章年份:光看檔名很難知道這是新文章還是舊文章
- 難以批次處理:想要對特定時期的文章做處理(例如更新 frontmatter)很麻煩
考慮過的方案
方案 1:檔名加日期前綴
docs/notes/
├── 2025-11-04-integrating-waline-comments.md
├── 2025-10-31-javascript-clean-code-practices.md
└── 2011-11-27-ecmascript-5-strict-mode.md優點:
- 實作超簡單,不用改任何設定
- 檔案列表自動依照日期排序
缺點:
- URL 變長:
/notes/2025-11-04-integrating-waline-comments.html - 日期資訊重複(frontmatter 已經有了)
- 檔名太長,不好辨識重點
方案 2:完全依照年月分類
docs/notes/2025/11/integrating-waline-comments.md
→ URL: /notes/2025/11/integrating-waline-comments.html優點:
- 檔案結構清晰
缺點:
- URL 層級太深,看起來冗長
- 所有現有 URL 都會改變,破壞 SEO
- 如果有內部參考連結也要更新
方案 3:VitePress Rewrites
檔案: docs/notes/2025/11/integrating-waline-comments.md
URL: /notes/integrating-waline-comments.html這就是我要的!
- 原始的 md 檔案依照年月分類,方便管理
- URL 保持簡潔扁平
- 完全相容(原有的 URL 不受影響)
實作步驟
第一步:設定 Rewrites
在 docs/.vitepress/config.ts 加入 rewrites 規則:
export default defineConfig({
// Rewrites: 將年月目錄結構的文章對外呈現為扁平 URL
rewrites: {
'notes/:year/:month/:article.md': 'notes/:article.md',
'learn/:year/:month/:article.md': 'learn/:article.md',
'misc/:year/:month/:article.md': 'misc/:article.md',
},
// 其他設定...
})這個規則的意思是:
- 實際檔案路徑:
notes/2025/11/my-article.md - 對外 URL:
/notes/my-article.html
在打包的時候 VitePress 會自動處理這個對應關係。
第二步:更新 Data Loader
這一步有兩個重點:
1. 確認掃描路徑包含子目錄
我的 posts.data.ts 原本就用 **/*.md 來掃描,所以不需要改:
export default createContentLoader([
'notes/**/*.md', // ** 會遞迴掃描所有子目錄
'learn/**/*.md',
'misc/**/*.md'
], {
excerpt: true,
transform(raw): Post[] {
// 資料處理...
}
})如果你的掃描路徑是寫死的(例如 notes/*.md),記得改成 notes/**/*.md。
2. 轉換 URL 為扁平結構
重要! VitePress 的 data loader 會根據實際檔案路徑生成 URL,所以需要手動轉換:
// 將年月目錄結構的 URL 轉換為扁平 URL(對應 VitePress rewrites 設定)
// 範例: /notes/2025/11/article.html → /notes/article.html
function rewriteUrl(url: string): string {
// 匹配格式: /(notes|learn|misc)/YYYY/MM/article.html
const match = url.match(/\/(notes|learn|misc)\/\d{4}\/\d{2}\/(.+\.html)/)
if (match) {
const [, category, filename] = match
return `/${category}/${filename}`
}
// 如果不符合年月格式,直接返回原 URL
return url
}
export default createContentLoader([...], {
excerpt: true,
transform(raw): Post[] {
return raw
.filter(...)
.map(({ url, frontmatter, excerpt }) => ({
title: frontmatter.title,
url: rewriteUrl(url), // ← 轉換 URL
excerpt,
// ...
}))
}
})如果不加這個轉換,首頁和分類頁的連結會指向 /notes/2025/11/article.html,導致 404 錯誤。
第三步:建立轉移的 script
這裡我寫了一個 script 來自動搬移所有文章到正確的年、月目錄。
const fs = require('fs');
const path = require('path');
const matter = require('gray-matter');
const categories = ['notes', 'learn', 'misc'];
categories.forEach(category => {
const categoryDir = path.join('docs', category);
const files = fs.readdirSync(categoryDir)
.filter(file => file.endsWith('.md') && file !== 'index.md');
files.forEach(filename => {
const sourcePath = path.join(categoryDir, filename);
const content = fs.readFileSync(sourcePath, 'utf-8');
const { data } = matter(content);
if (!data.date) {
console.log(`跳過(無日期): ${filename}`);
return;
}
// 解析日期
const date = new Date(data.date);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
// 建立目標路徑
const targetDir = path.join(categoryDir, String(year), month);
const targetPath = path.join(targetDir, filename);
// 建立目錄並移動檔案
if (!fs.existsSync(targetDir)) {
fs.mkdirSync(targetDir, { recursive: true });
}
fs.renameSync(sourcePath, targetPath);
console.log(`已移動: ${filename} → ${year}/${month}/`);
});
});最終效果
開發環境(檔案結構)
docs/notes/
├── 2025/
│ └── 11/
│ ├── integrating-waline-comments-to-vitepress.md
│ ├── javascript-clean-code-practices.md
│ └── vite-optimization-guide.md
├── 2017/
│ ├── 09/
│ ├── 08/
│ └── 07/
├── 2015/
│ ├── 05/ (6 篇文章)
│ ├── 04/
│ └── ...
└── index.md優點:
- IDE 側邊欄可以依照年份折疊,方便瀏覽
- 可以快速找到特定年份或月份的文章
- 新增文章時很清楚該放哪裡
正式環境(URL 結構)
所有 URL 保持簡潔扁平:
/notes/integrating-waline-comments-to-vitepress.html
/notes/javascript-clean-code-practices.html
/notes/ecmascript-5-strict-mode.html優點:
- URL 簡短易記
- 對 SEO Friendly
- 舊的分享連結完全不受影響
新增文章的流程
以後新增文章只需要:
確認日期:例如今天是 2025-11-06
建立年月目錄(如果不存在):
bashmkdir -p docs/notes/2025/11建立文章檔案:
bashdocs/notes/2025/11/my-new-article.md寫入 Frontmatter:
markdown--- title: 我的新文章 date: 2025-11-06 tags: [vitepress, vue] ---完成! VitePress 會自動:
- 掃描到這篇文章
- 產生
/notes/my-new-article.htmlURL - 加入文章列表和標籤頁面
注意事項
1. 檔名不能重複
因為 URL 是扁平的,所以即使在不同年月目錄,檔名也不能重複。
錯誤示範:
docs/notes/2025/11/vue-tips.md
docs/notes/2024/05/vue-tips.md ← 衝突!兩個檔案都會產生 /notes/vue-tips.html,後者會覆蓋前者。
正確做法:
docs/notes/2025/11/vue-composition-api-tips.md
docs/notes/2024/05/vue-options-api-tips.md2. Data Loader 需要重啟
修改 frontmatter 或新增檔案後,需要重啟開發伺服器:
# Ctrl+C 停止,然後重新啟動
pnpm devVitePress 的 data loader 會快取結果,不重啟的話看不到新文章。
3. Tag 路由也要更新掃描路徑
如果你有動態標籤頁面(docs/tags/[tag].paths.ts),確認掃描路徑有包含所有子目錄:
const posts = await createContentLoader([
'notes/**/*.md', // ← 確保有 **
'learn/**/*.md',
'misc/**/*.md'
]).load()總結
VitePress 的 rewrites 功能真的很強大,讓我可以:
- 在開發時享受有組織的檔案結構
- 在正式環境保持簡潔的 URL
- 完全相容,不會破壞任何現有連結
這次重構花了大約一小時,包含寫轉移的 script、測試、更新文件。 如果你的 VitePress 部落格也有檔案管理問題,非常推薦試試這個方法!
相關閱讀: