打造這個網站
為什麼要從零打造個人網站?
市面上有很多網站建立工具和模板,為什麼還要從零開始?對我而言,答案只有一個:我想掌握每一個細節——字體、色盤、程式碼區塊的呈現方式,甚至段落之間的間距。個人網站反映你如何看待 craft,我希望它看起來是有意識地被做出來的。
目標是優雅的簡單:網站要極簡,但不能空洞。每個設計決定都應該值得存在。
這篇會用實際實作中的程式碼,說明支撐這個網站的幾個關鍵技術選擇。
技術堆疊
以下是驅動本站的核心技術概覽:
| 技術 | 版本 | 角色 |
|---|---|---|
| Next.js | 16.x | 使用 App Router 的全端 React framework |
| React | 19.x | 透過 Server Components 渲染 UI |
| TypeScript | 5.x | 為整個 codebase 提供型別安全 |
| Tailwind CSS | 4.x | 採 CSS-first 設定的 utility-first styling |
| MDX | @next/mdx | 以 React components 撰寫部落格內容 |
| Shiki | 透過 rehype-pretty-code | 建置時語法高亮 |
為什麼選 Next.js 16
Next.js 16 預設提供穩定版 Turbopack、支援 React 19,也有成熟的 App Router。對一個主要依賴 static generation 的個人網站來說,App Router 的 generateStaticParams 能在 build 時輕鬆預先渲染每篇文章。執行時不必有伺服器,只要把 static HTML 放在 CDN。
設定也很精簡。以下是完整的 next.config.ts:
import type { NextConfig } from "next";
import createMDX from "@next/mdx";
const nextConfig: NextConfig = {
pageExtensions: ["ts", "tsx", "md", "mdx"],
};
const withMDX = createMDX({
options: {
remarkPlugins: [],
rehypePlugins: [],
},
});
export default withMDX(nextConfig);pageExtensions 讓 Next.js 把 .mdx 視為有效頁面,createMDX 則接上 MDX compiler。較重的工作——syntax highlighting 與 GFM 支援——會在 src/lib/mdx.ts 的獨立編譯步驟中完成。
為什麼選 Tailwind CSS v4
Tailwind v4 引入 CSS-first configuration。設計 token 不再放在 tailwind.config.js,而是用 @theme directive 直接寫在 CSS 裡。整個 design system 因此集中在 globals.css,不必拆散在 JavaScript 與 CSS 檔案中。
@import "tailwindcss";
@plugin "@tailwindcss/typography";
@custom-variant dark (&:is(.dark *));
@theme inline {
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
--font-heading: var(--font-geist-sans);
--color-background: var(--background);
--color-foreground: var(--foreground);
}@custom-variant dark 是 v4 的 dark mode 寫法,取代舊的 darkMode: 'class' 設定。搭配 next-themes 後,網站會尊重系統偏好,也能手動切換,不會在載入時閃現錯誤主題。
以 MDX 把內容當成程式碼
本站每篇文章都是 content/blog/ 裡的 .mdx 檔案。MDX 讓你在標準 Markdown 裡嵌入 React components,兼具兩者優點。文章和程式碼一起 version-control,不必管理外部 CMS;內容 pipeline 也只是一個 function call。
文章如何編譯
編譯 pipeline 刻意保持簡單。每篇文章的原始 MDX string 會在 server 端以 @mdx-js/mdx 編譯:
import { compile, run } from "@mdx-js/mdx";
import remarkGfm from "remark-gfm";
import rehypePrettyCode from "rehype-pretty-code";
export async function compileMDX(source: string) {
const compiled = await compile(source, {
outputFormat: "function-body",
remarkPlugins: [remarkGfm],
rehypePlugins: [
[rehypePrettyCode, {
theme: { dark: "github-dark", light: "github-light" },
keepBackground: false,
}],
],
});
const { default: MDXContent } = await run(
String(compiled),
{ ...runtime, baseUrl: import.meta.url }
);
return MDXContent;
}remarkGfm 開啟 GitHub Flavored Markdown,例如表格、刪除線與 task list。rehypePrettyCode 使用 Shiki 在 build 時 tokenize code block,因此 syntax highlighting 已經烘進 HTML,瀏覽器端不需要額外 JavaScript。
自訂元件
MDX components 會在 src/components/mdx/mdx-components.tsx 對應。每個 <pre> block 都會包上複製按鈕和語言 badge;圖片則透過 next/image 自動最佳化與 lazy load。
這個做法的優點是你只要寫一般 Markdown:
# 一個標題
含有 `inline code` 與 code block 的文字:component map 會把它轉成有樣式、可互動的 HTML,不需要特殊語法。
設計哲學
設計遵循一條簡單規則:不服務內容的東西,就不該存在。排版使用 Geist Sans 做內文、Geist Mono 顯示程式碼;兩者都乾淨、易讀,在任何大小下都表現良好。
色彩 Token
色彩系統採用 oklch——比 hex 或 HSL 更能產生自然色盤的感知色彩空間。light mode 使用暖中性色,dark mode 則轉為冷色調。每個顏色都是 CSS custom property,並透過 Tailwind 的 theme layer 對應:
:root {
--background: oklch(0.98 0.005 90);
--foreground: oklch(0.145 0.02 90);
--muted: oklch(0.94 0.008 90);
--muted-foreground: oklch(0.42 0.015 90);
}Typography
部落格內容使用 @tailwindcss/typography 提供的 Tailwind prose utility class。它帶來好讀的 line length、正確的 heading hierarchy、blockquotes 與 inline code 樣式,不必手寫 custom CSS。dark:prose-invert variant 會自動處理 dark mode。
下一步
這個網站是一個持續演進的 project。接下來預計會做:
- 展示精選作品的 portfolio section
- 透過 custom route handler 產生 RSS feed
- 使用 Resend 製作具有 server-side validation 的 contact form
- 效能調校與 Lighthouse score 最佳化
Codebase 是開放的,內容也受到 version control 管理。每項改進都會以一個 commit 發佈。