← Work
yoAkyoku 夜明曲 工作室網站
這個網站本身。Next.js 編輯式版面、Payload 後台、依節氣自動換季的主題引擎,以及一個永遠不洩漏個人資料的內容邊界。
- Web
- Tools

Contents
概述
工作室的門面,也是工作室的第一個作品。目標很單純:把作品與筆記說清楚,畫面安靜、留白多、讀起來舒服;同時要有一個真的會用的後台,而不是每次改文案都要開 editor 推 commit。
問題
- 作品與筆記需要長期維護,Markdown in Git 對一個人夠用,但「發佈」等於「部署」,改一個錯字也要跑一次 CI。
- 網站要隨季節換裝(過年、開花、海灘、煙花、落葉、下雪),這是狀態,不該寫死在程式碼裡。
- 網站不能出現創作者的真名、照片、地點、雇主或學經歷。這條規則必須是結構性的,而不是靠每次 review 記得。
解法
前端是 Next.js App Router,內容全部走一個「內容模組」的窄介面;後台是 Payload CMS,掛在同一個 repo 的 /admin。主題引擎用 24 節氣與農曆年當邊界,自動選出 7 個主題之一,後台可以覆蓋。
content/*.mdx ──seed──▶ Payload (SQLite / Neon)
│
src/lib/content ◀── 唯一的內容入口
│
getProjects / getProject / getNotes / getNote / getSearchIndex
│
pages ──▶ <Body> ──▶ Lexical → JSX (Shiki tokens)
season.ts (節氣) ──▶ getTheme() ◀── Settings.themeOverride
│
<html data-theme=…> ──▶ CSS tokens + 粒子層主要功能
- Work / Notes / Studio / Character 四個區塊,作品與筆記可互相關聯,任一邊宣告即可。
- 後台:作品、筆記、媒體、網站設定(主題覆蓋、每季 Hero 素材)。內文是 Lexical,加上四種自訂區塊:Diagram、Code、Screens、Callout。
- 季節主題:七個主題以節氣為邊界自動切換,
?theme=可預覽任何一季。 - 搜尋:靜態索引,鍵盤可操作的 combobox。
- SEO 與分享:sitemap、每頁 canonical、筆記 RSS;沒有封面的文章自動產生含標題的分享卡片。
架構
src/
├── app/(site)/ 頁面,只呼叫 src/lib/content 與 src/lib/theme
├── app/(payload)/ Payload admin 與 REST
├── lib/content/ 內容模組:Payload adapter、標題 id、搜尋索引
├── lib/season*.ts 主題字彙(client-safe)與節氣邊界(server only)
├── components/lexical 伺服器端 Lexical → JSX 轉換器
└── payload/ collections、globals、access、hooks工程決策
- 深模組:頁面看不到 Payload。內容模組回傳的是已解析好的型別,換掉資料來源時頁面一行都不用改。這次從 MDX 換成 Payload 就是這樣做的。
- 不用 HTML 字串:程式碼高亮用 Shiki 的 token 直接產出 JSX,內文沒有任何
dangerouslySetInnerHTML。 - 主題只動 token:換季改的是 CSS 變數與一層裝飾粒子,版面與字體不變,所以任何一季都不會壞版。
- 本機零依賴:開發用 SQLite 與本機上傳;設定
postgres://與 Blob token 就自動切到 Neon 與 Vercel Blob。 - 內容可攜:後台內容可以匯出回 MDX,與匯入往返零差異。換機器與備份都靠這條路。
- 安全預設:匿名只能讀已發佈內容、GraphQL 關閉、上傳只收 png/jpeg/webp、登入路由限流、CSP 與安全標頭在
next.config。
畫面



技術
Next.js 16(App Router、Turbopack)、React 19、TypeScript、Tailwind v4、Payload CMS 3(Lexical、SQLite / Postgres、Vercel Blob)、Shiki、lunar-javascript、Vercel。
結果與心得
上線之後,內容更新不再需要部署。最有價值的其實是內容模組那個窄介面:它讓「換 CMS」變成一個下午的事,也讓隱私規則可以在一個地方檢查。