Ewan(Pizza)的個人技術部落格,部署於 https://pizzanote.dev。繁體中文與英文雙語,內容以 Markdown 存在這個 repo,build 時全部預先渲染成靜態頁面。
- 框架:Next.js 16(App Router)、React 19、Tailwind CSS 4、TypeScript
- 內容:
content/<locale>/底下的 Markdown,沒有 CMS、沒有執行期資料庫查詢 - 部署:Vercel(push 到 GitHub 就會 build;branch 會產生 preview)
- 語言:
zh-tw(預設)、en,網址帶語言前綴
npm install
npm run dev # http://localhost:3000,根目錄會導向 /zh-tw/
npm run build # 先跑 check:content,再 next build
npm run lint
npm run typecheck
npm test只需要一個環境變數,其餘功能沒設定時會自動關閉(見下方):
cp .env.example .env.localcontent/
├─ zh-tw/
│ ├─ taxonomy.json 主題(topics)、分類(categories)、標籤(tags)
│ └─ <topic>/<slug>.md 文章
├─ en/ 同上,英文版
└─ views-seed.json 舊的瀏覽數,作為 Redis 計數器的初始值
-
在
content/zh-tw/<topic>/<slug>.md新增檔案;英文版放在content/en/<topic>/<slug>.md,兩個語言的slug要相同(hreflang靠它對應)。 -
frontmatter:
--- title: "文章標題" # 搜尋結果顯示,建議 ≤ 60 字元 description: "一兩句摘要" # 建議 50–160 字元 publishedAt: "2025-06-24T06:57:07.835Z" updatedAt: "2025-06-24T06:57:07.835Z" # 內容有實質修改時更新;會成為 sitemap 的 lastmod category: "javascript" # 必須存在於 taxonomy.json series: "this 物件" # 選填,同一分類內的系列分組 tags: ["react"] # 選填,必須存在於 taxonomy.json aliases: ["OldSlug"] # 選填,舊網址;會自動 301 到新網址 ---
-
內文不要再寫
# 標題(標題由 frontmatter 渲染成唯一的<h1>),章節從##開始。 -
圖片放
public/images/posts/<slug>/,在文章中用/images/posts/<slug>/01.png引用。 -
npm run check:content會檢查格式、分類/標籤是否存在、圖片是否存在,並警告中英文章更新日期差太多(翻譯可能過期)。
網址規則:
| 頁面 | 網址 |
|---|---|
| 文章 | /<locale>/<topic>/<slug>/ |
| 主題 | /<locale>/<topic>/ |
| 分類 | /<locale>/category/<category>/ |
| 標籤 | /<locale>/tags/<tag>/(標籤至少 2 篇文章才會產生頁面) |
| 純 Markdown 版本 | /<locale>/<topic>/<slug>.md |
沒有任何文章的主題不會產生頁面,也不會出現在選單。
sitemap.xml(含 lastmod 與 hreflang)、robots.txt(明確允許 AI 爬蟲)、/<locale>/feed.xml(RSS)、llms.txt、/<locale>/search-index.json(站內搜尋)、每篇文章的 JSON-LD(BlogPosting、BreadcrumbList)。
舊網址(遷移前的 /<topic>/<post> 與 Sanity 時期的大小寫 slug)由 config/legacy-redirects.mjs 從內容產生 301。
| 功能 | 環境變數 | 說明 |
|---|---|---|
| 留言(giscus) | NEXT_PUBLIC_GISCUS_REPO_ID、NEXT_PUBLIC_GISCUS_CATEGORY_ID(另有 NEXT_PUBLIC_GISCUS_REPO、NEXT_PUBLIC_GISCUS_CATEGORY) |
需在 GitHub repo 開啟 Discussions 並安裝 giscus app |
| 瀏覽次數 | KV_REST_API_URL、KV_REST_API_TOKEN(也接受 UPSTASH_REDIS_REST_URL/TOKEN) |
透過 Vercel Marketplace 加入 Upstash Redis(vercel integration add upstash/upstash-kv),變數會自動注入;頁面本身仍是靜態的,數字由瀏覽器載入後取得 |
| 正式網域 | NEXT_PUBLIC_SITE_URL |
canonical、sitemap、Open Graph 都以它為準(預設 https://pizzanote.dev,不含 www) |
src/
├─ app/
│ ├─ [locale]/… 頁面(首頁、about、posts、[topic]、[topic]/[post]、category、tags、feed、search-index)
│ ├─ api/views/ 瀏覽數 API(唯一的動態端點)
│ ├─ md/… 純 Markdown 版本(透過 next.config 的 rewrite 對外為 <slug>.md)
│ ├─ sitemap.ts、robots.ts、llms.txt/
│ └─ not-found.tsx
├─ components/ UI 元件(ui/ 為 shadcn)
├─ i18n/ 語言設定與字典
└─ lib/ content.ts(讀 Markdown)、seo.ts(metadata 與 JSON-LD)、urls.ts、site.ts
config/legacy-redirects.mjs
scripts/check-content.mjs