簡單來說
記錄如何在 Astro + MDX 專案中整合 React 組件,打造包含社群連結、技能標籤和時間軸的精美 About 頁面,以及在白色背景下優化配色的實戰經驗。
前言
一個好的個人網站少不了一個精心設計的「關於我」頁面。今天的任務是為部落格的 About 頁面創建三個互動式 React 組件:
- SocialLinks - 社群媒體連結卡片
- TechStack - 技能標籤展示
- Timeline - 職涯成長時間軸
這些組件不僅要好看,還要在白色背景下有良好的可讀性和對比度。
技術選型與架構
技術棧
- Astro - 靜態站點生成器,支援 Islands Architecture
- React - 用於構建互動組件
- MDX - 在 Markdown 中嵌入 React 組件
- Tailwind CSS - 快速樣式開發
- lucide-react - 現代化的圖標庫
為什麼這樣選?
Astro 的 Islands Architecture 讓我可以只在需要的地方注入 JavaScript,
其他部分保持靜態 HTML,兼顧互動性與效能。
開發過程
第一步:安裝依賴
pnpm add lucide-react
lucide-react 是輕量級的圖標庫,比傳統的 Font Awesome 更適合現代化專案。
第二步:組件文件結構
根據 MDX 的 import 路徑規則,組件需要放在相對路徑可訪問的位置:
components/
├── SocialLinks.tsx
├── TechStack.tsx
└── Timeline.tsx
組件設計與實作
1. SocialLinks - 社群連結組件
設計目標:
- 圓角矩形卡片設計
- 每個平台有專屬 hover 配色
- 平滑的縮放與顏色過渡動畫
核心程式碼:
import { Github, Twitter, Mail, Linkedin } from "lucide-react";
export function SocialLinks() {
return (
<div className="not-prose flex flex-wrap gap-3 my-6">
{links.map(({ href, label, icon: Icon, hoverBg, hoverText }) => (
<a
href={href}
className={`
inline-flex items-center gap-2 px-4 py-2
rounded-lg border border-gray-200
bg-gray-50 text-gray-600
transition-all duration-300
${hoverBg} ${hoverText}
hover:shadow-md hover:scale-105
`}
>
<Icon size={18} />
<span>{label}</span>
</a>
))}
</div>
);
}
關鍵技巧:
- 使用
not-proseclass 避免被 Tailwind Typography 影響 - 動態 className 讓每個按鈕有不同的 hover 效果
transition-all duration-300創造平滑動畫
2. TechStack - 技能標籤組件
設計目標:
- 每個技術有專屬配色
- Flex wrap 自動換行
- Hover 時微縮放效果
核心程式碼:
const colorMap: Record<string, { bg: string; border: string; text: string }> = {
React: { bg: "bg-sky-200", border: "border-sky-600", text: "text-sky-900" },
TypeScript: { bg: "bg-blue-200", border: "border-blue-600", text: "text-blue-900" },
TailwindCSS: { bg: "bg-teal-200", border: "border-teal-600", text: "text-teal-900" },
// ...更多配色
};
export function TechStack({ languages }: TechStackProps) {
return (
<div className="not-prose flex flex-wrap gap-2.5 my-4">
{languages.map((lang) => {
const color = colorMap[lang] ?? defaultColor;
return (
<span className={`${color.bg} ${color.border} ${color.text} ...`}>
{lang}
</span>
);
})}
</div>
);
}
調色心得:
- 初版使用
-50和-100的淺色,在白背景下幾乎看不見 - 最終定案:背景使用
-200,邊框-600,文字-900 - 這樣的組合在白色背景下有極佳的對比度
3. Timeline - 時間軸組件
設計目標:
- 左側垂直線 + 圓圈節點
- 右側內容區(年份、標題、描述)
- Hover 時圓圈放大並發光
- 支援彩色 badge 標籤
核心程式碼:
export function Timeline({ items }: TimelineProps) {
return (
<div className="not-prose relative my-8">
{items.map((item, index) => (
<div className="group relative flex gap-6 pb-10">
{/* 左側時間軸 */}
<div className="relative flex flex-col items-center">
<div className="
rounded-full border-2 border-blue-600 bg-white
group-hover:scale-150 group-hover:bg-blue-600
group-hover:shadow-[0_0_12px_rgba(37,99,235,0.6)]
" />
{/* 垂直連接線 */}
<div className="absolute top-4 h-full w-0.5 bg-gray-300" />
</div>
{/* 右側內容 */}
<div className="flex-1">
<span className="text-blue-800 font-bold">{item.year}</span>
<h3 className="text-gray-900 font-bold">{item.title}</h3>
<p className="text-gray-800">{item.description}</p>
</div>
</div>
))}
</div>
);
}
互動亮點:
- 使用 Tailwind 的
group和group-hover實現整體 hover 效果 - 圓圈節點的發光效果使用
shadow-[0_0_12px_rgba(...)]自訂陰影 scale-150讓圓圈在 hover 時放大,視覺反饋明顯
踩坑與解決方案
問題 1:MDX Import 順序錯誤
錯誤訊息:
[@mdx-js/rollup] Could not parse import/exports with acorn
Unexpected character ' '
原因:
在 MDX 中,所有 import 語句必須放在 frontmatter 之後、Markdown 內容之前。我一開始把標題寫在 import 前面了。
解決方案:
---
title: 關於我
---
import { SocialLinks } from './components/SocialLinks';
import { TechStack } from './components/TechStack';
import { Timeline } from './components/Timeline';
# 正文內容開始
問題 2:白色背景下對比度不足
遇到的狀況:
- TechStack 的標籤顏色太淺,幾乎看不清楚
- Timeline 的文字是
gray-600,在白底上灰濛濛的
解決歷程:
| 嘗試次數 | 配色方案 | 結果 |
|---|---|---|
| v1 | bg-50, text-700 | ❌ 太淺,看不清 |
| v2 | bg-100, text-800 | ⚠️ 有改善但還是不夠 |
| v3 | bg-200, text-900 | ✅ 完美!對比度極佳 |
最終方案:
// TechStack 配色
{
bg: "bg-sky-200", // -200 提供明顯的背景色
border: "border-sky-600", // -600 清晰的邊框
text: "text-sky-900" // -900 最深的文字顏色
}
// Timeline 文字顏色
year: "text-blue-800"
title: "text-gray-900"
description: "text-gray-800"
關鍵經驗:
在設計白色背景的 UI 時,不要害怕使用深色(-800 / -900)。 「看起來太重」通常意味著「對比度剛剛好」。
問題 3:Vercel 部署時的 Symlink 錯誤
錯誤訊息:
EPERM: operation not permitted, symlink
原因:
Windows 系統下建立 symlink 需要管理員權限,這是 @astrojs/vercel adapter 的已知問題。
解決方案: 這個錯誤只會出現在本地建置時,實際部署到 Vercel 時不會出現。如果真的需要在本地完整測試 Vercel 建置,可以用管理員權限執行 terminal。
在 MDX 中使用組件
完成組件後,在 about.mdx 中這樣使用:
---
title: 關於我
publishedDate: 2026-02-17
---
import { SocialLinks } from './components/SocialLinks';
import { TechStack } from './components/TechStack';
import { Timeline } from './components/Timeline';
## 👋 嗨,我是 Sai
<SocialLinks />
## 🛠 技能清單
<TechStack
languages={["React", "TypeScript", "TailwindCSS", "Node.js"]}
/>
## ⏳ 成長軌跡
<Timeline
items={[
{
year: "2023 - 現在",
title: "踏入程式開發領域",
description: "專注於構建高效能的 React 應用...",
badge: "進行中"
}
]}
/>
成果展示
最終實現的效果:
✅ SocialLinks
- 四個社群平台按鈕(GitHub, Twitter, Email, LinkedIn)
- 每個按鈕 hover 時有專屬配色(灰、藍、紅、藍)
- 平滑的縮放動畫
✅ TechStack
- 彩色技能標籤,自動換行
- 明確的視覺層次感
- 在白色背景下有極佳的可讀性
✅ Timeline
- 垂直時間軸設計
- 圓圈節點 hover 時放大發光
- 彩色 badge 顯示狀態(進行中、已完成、起點)
效能考量
使用 Astro 的好處是這些 React 組件只在必要時才載入 JavaScript:
# 查看建置結果
pnpm build
# 頁面體積分析
✓ /posts/about/index.html (+52ms)
- Static HTML: 98%
- JavaScript: 2% # 只有互動組件需要 JS
對比傳統的純 React SPA,這種 Partial Hydration 策略讓頁面載入速度快得多。
總結與反思
這次學到的重點
- 配色永遠比想像中重要 - 好的 UI 需要足夠的對比度
- MDX 有自己的規則 - Import 順序不能亂
- Astro Islands 是真的香 - 在需要的地方用 React,其他部分保持靜態
可以改進的地方
- 增加響應式斷點,手機版調整版面
- Timeline 可以加入展開/收合功能
- SocialLinks 圖標可以加載失敗處理
下一步計畫
- 為其他頁面添加更多互動組件
- 實作深色模式切換(目前只針對白色背景優化)
- 考慮使用 Framer Motion 增加更流暢的動畫
相關資源
如果你也在打造自己的個人網站,希望這篇文章對你有幫助!🚀