環境架構設計
- astro v5
- tailwind v4
- vercel 佈署
第一階段:環境初始化
1. 建立專案
打開終端機,執行 Astro 建立指令:npm create astro@latest my-product-explorer
# 選擇:Empty project
# 選擇:Yes (Install dependencies)
# 選擇:Yes (TypeScript - Strict)
2. 安裝 Tailwind v4
在 v4 中,我們不再使用舊的 @astrojs/tailwind 整合包,而是直接使用 Vite 插件:
npm install tailwindcss @tailwindcss/vite
3. 安裝 vercel
npx astro add vercel安裝這個適配器主要是為了將 Astro 從「靜態產生」轉向 SSR(伺服器端渲染) 或混合模式。它的主要優點包括:
- SSR 支援:讓你可以在 Vercel 的 Edge Network 或 Serverless Functions 上執行後端程式碼(例如處理登入、API 請求)。
- 預覽功能:支援 Vercel 的「草稿模式(Draft Mode)」。
- 影像優化:自動與 Vercel 的圖片優化服務整合。
- 中介軟體 (Middleware):讓你能直接在 Vercel 邊緣節點執行邏輯。
💡 Serverless Functions :詳見 額外筆記-⭐ Serverless Functions
4. 設定 astro.config.mjs
將 Tailwind 整合進 Vite 配置:
import { defineConfig } from 'astro/config'
import tailwindcss from '@tailwindcss/vite'
import vercel from '@astrojs/vercel'
export default defineConfig({
adapter: vercel(),
output: 'server', // 為了儀表板,通常建議使用 SSR 或 Hybrid
vite: {
plugins: [tailwindcss()],
},
i18n: {
defaultLocale: 'en',
locales: ['en', 'zh-tw'],
routing: {
prefixDefaultLocale: false,
},
},
})
💡 i18n :詳見 額外筆記-⭐ i18n (國際化多語系)
第二階段:Tailwind v4 CSS-First 配置
在 src/styles/global.css 中,直接引入 Tailwind 並定義主題:
@import 'tailwindcss';
@theme {
/* 定義儀表板專用顏色 */
--color-dashboard-bg: #f8fafc;
--color-brand: #3b82f6;
}
/* v4 不再需要 @tailwind base/components/utilities; 只要 @import 即可 */
在你的 Layout 檔案(如 src/layouts/Layout.astro)中引入此 CSS:
---
import '../styles/global.css';
---
<html>...</html>
第三階段:實作多語系商品資料 (Content Layer)
Astro v5 強大的地方在於 Content Layer。
1. 定義 Schema
在 src/content/config.ts 定義商品資料結構:
import { defineCollection, z } from 'astro:content'
import { glob } from 'astro/loaders'
const products = defineCollection({
loader: glob({
pattern: '**/[^_]*.{md,mdx}',
base: './src/content/products',
}),
schema: z.object({
title: z.string(),
description: z.string(),
price: z.number(),
category: z.string(),
}),
})
export const collections = { products }
💡 'astro:content' 錯誤訊息 :
可詳見 額外筆記-⚠️ 如果 "astro:content" IDE 出現錯誤
💡 glob :
可詳見 額外筆記-⭐ import { glob } from 'astro/loaders';
💡 collections :
可詳見 額外筆記-⭐ export const collections = { products }
2. 檔案結構
建立多語系資料夾:
src/content/products/en/iphone.md---
title: 'iPhone 15 Pro'
description: 'Experience the next generation of iPhone with Titanium design and A17 Pro chip.'
price: 999
category: 'Smartphones'
---
## Product Features
- **Titanium Design**: Strong and light.
- **A17 Pro Chip**: A monster win for gaming.
- **Camera**: 48MP Main camera for super-high-resolution photos.
This is the English version of the product description.src/content/products/zh-tw/iphone.md---
title: 'iPhone 15 Pro'
description: '體驗配備鈦金屬設計與 A17 Pro 晶片的次世代 iPhone。'
price: 36900
category: '智慧型手機'
---
## 產品特點
- **鈦金屬設計**:堅固且輕盈。
- **A17 Pro 晶片**:遊戲效能的巨大飛躍。
- **相機系統**:4800 萬像素主相機,打造超高解析度照片。
這是產品說明的繁體中文版本。
第四階段:建立導覽與儀表板 UI
1. 語言切換組件
建立 src/components/LanguagePicker.astro:
---
const languages = { en: 'English', 'zh-tw': '繁體中文' };
---
<ul class="flex gap-4">
{Object.entries(languages).map(([code, label]) => (
<li>
<a href={`/${code}/`} class="text-brand hover:underline">{label}</a>
</li>
))}
</ul>
2. 商品列表頁碼
在 src/pages/[lang]/products.astro:
---
import { getCollection } from 'astro:content';
export async function getStaticPaths() {
return [{ params: { lang: 'en' } }, { params: { lang: 'zh-tw' } }];
}
const { lang } = Astro.params;
const allProducts = await getCollection('products', ({ id }) => id.startsWith(lang));
---
<div class="p-8 bg-dashboard-bg min-h-screen">
<h1 class="text-3xl font-bold mb-6">Product Explorer</h1>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
{allProducts.map(product => (
<div class="bg-white p-4 rounded-xl shadow-sm border border-gray-200">
<h2 class="text-xl font-semibold">{product.data.title}</h2>
<p class="text-gray-500">{product.data.description}</p>
<div class="mt-4 font-mono text-brand">${product.data.price}</div>
</div>
))}
</div>
</div>
💡 getCollection :
可詳見 額外筆記-⭐ const allProducts = await getCollection('products', ({ id }) => id.startsWith(lang));
💡 getCollection 錯誤訊息 :
可詳見 額外筆記-⚠️ 如果 ...getCollection... IDE 出現錯誤
第五階段:Vercel 佈署
1. 提交至 GitHub:
git init
git add .
git commit -m "feat: init multi-lang dashboard"
2.連結 Vercel:
- 登入 Vercel。
- 點擊 Add New Project,導入你的 GitHub 倉庫。
- Vercel 會自動偵測 Astro 框架,並使用
npm run build進行構建。
📚 額外筆記
⭐ Serverless Functions
Serverless Functions(無伺服器函數)是一種雲端運算模型,讓你只需要編寫程式碼(函數),而不需要管理底層的伺服器硬體或作業系統。
雖然名字叫「無伺服器」,但並不是真的沒有伺服器,而是伺服器管理的所有瑣事都由雲端供應商(如 AWS, Google Cloud)幫你處理好了。
💡 核心特點:為什麼它很受歡迎?
1. 事件驅動 (Event-Driven): 函數平常是不運行的,只有在特定的「事件」觸發時才會啟動。例如:有人上傳照片、呼叫了 API、或是定時排程。
2. 自動擴展 (Auto-scaling): 如果你的網站突然湧入一萬個人,雲端系統會自動幫你啟動一萬個函數實例;沒人用的時候,它會縮減到零。
3. 按量計費 (Pay-as-you-go): 你只按照程式碼「執行」的時間付費。如果程式沒跑,你一分錢都不用花。這跟傳統租用伺服器(不管有沒有人用都要付月費)完全不同。
4. 無狀態 (Stateless): 函數執行完就會消失,不會記住上次的狀態。如果需要儲存資料,必須另外存到資料庫或雲端硬碟。
💡 常見的使用場景
- 圖片處理: 使用者上傳照片後,自動產生縮圖。
- API 後端: 處理簡單的網頁請求(如登入、查詢訂單)。
- 定時任務: 每天早上 8 點自動抓取天氣預報並發送通知。
- 數據清洗: 將進入資料庫的原始數據進行即時格式轉換。
💡 著名的服務供應商
目前市面上最主流的 Serverless Functions 服務包括:
- AWS Lambda (業界最領先)
- Google Cloud Functions
- Azure Functions
- Vercel / Netlify Functions (前端開發者常用)
⭐ i18n (國際化多語系)
...
export default defineConfig({
...
i18n: {
defaultLocale: 'en',
locales: ['en', 'zh-tw'],
routing: {
prefixDefaultLocale: false,
},
},
})
1. defaultLocale: "en"
這定義了網站的 預設語言。
- 在這裏,你的主要語系設定為英文。
- 當使用者訪問根目錄(例如 example.com/)時,系統會預設提供英文內容。
2. locales: ["en", "zh-tw"]
這定義了網站 支援的所有語系。
- 你的網站目前支援兩種語言:英文 (en) 和 繁體中文 (zh-tw)。
- Astro 會根據這個清單來產出對應的路由路徑。
3. routing: { prefixDefaultLocale: false }
這是關於 URL 網址路徑 的關鍵設定,決定了「預設語言」是否要在網址中顯示前綴(Prefix)。
- 設定為 false (目前的狀態):英文(預設):網址不會有前綴。例如:example.com/about中文:網址會有前綴。例如:example.com/zh-tw/about
- 如果設定為 true:英文:網址也會帶前綴。例如:example.com/en/about中文:保持不變。例如:example.com/zh-tw/about
⚠️ 如果 "astro:content" IDE 出現錯誤
Cannot find module 'astro:content' or its corresponding type declarations.
這個錯誤在 Astro 開發中非常常見,通常是因為 TypeScript 找不到自動生成的類型定義檔,或者是專案尚未進行第一次編譯。
以下是修復這個問題的步驟,按優先順序排列:
① 執行開發指令 (最有效)
Astro 的內容集合(Content Collections)類型是動態生成的。你需要執行一次開發伺服器或建構指令,讓 Astro 產生 .astro/ 資料夾裡的類型定義。
請在終端機執行:
npm run dev
# 或者
npx astro sync
astro sync 專門用於同步類型(如 astro:content),執行完後通常錯誤就會消失。
② 檢查 tsconfig.json
Astro 依賴 tsconfig.json 來對應路徑。請確保你的設定檔中有繼承 Astro 的預設設定:
{
"extends": "astro/tsconfigs/strict",
"compilerOptions": {
"baseUrl": ".",
"paths": {
"astro:content": ["./.astro/types.d.ts"]
}
}
}
注意: 如果你的 tsconfig.json 缺少 "extends": "astro/...",TypeScript 就不知道去哪裡找 astro:content。
③ 重啟 VS Code 的 TS 伺服器
有時候檔案已經產生了,但 VS Code 還沒反應過來。
- 在 VS Code 按下
Ctrl + Shift + P(Mac 是Cmd + Shift + P)。 - 輸入並選擇 "TypeScript: Restart TS Server"。
④ 確認 src/content/config.ts 是否存在
astro:content 需要你定義了內容設定才會生效。請確認你有這個檔案:
- 路徑:
src/content/config.ts - 內容範例:
import { defineCollection, z } from 'astro:content'
const blog = defineCollection({
/* ... */
})
export const collections = { blog }
⭐ import { glob } from 'astro/loaders';
這是在 Astro 5.0 版本引入的新功能:Content Layer API 中的一部分。
簡單來說,glob 是一個載入器(Loader),它讓你可以用更現代、效能更好的方式,將專案中的本地檔案(如 Markdown、JSON、YAML)載入到 Astro 的「內容集合(Content Collections)」中。
💡 它與舊版的區別
在 Astro 舊版本中,你不需要寫 loader,只要把檔案放在 src/content/ 下,系統會自動抓取。但在 Astro 5 中,透過 glob,你可以有更高的彈性:
- 1. 檔案位置不再受限:你的 Markdown 檔案可以放在專案的任何地方(例如
src/data/或src/posts/),不再強制只能放src/content/。 - 2. 效能優化:它只會掃描你指定的檔案路徑,並且在處理大量資料時速度更快。
- 3. 自定義模式:你可以精確控制哪些檔案要被當作內容。
⭐ export const collections = { products };
collections
是 Astro 框架的約定優於配置 (Convention over Configuration) 設計。
src/content/config.ts 是一個特殊的檔案,Astro 的內部引擎會專門去讀取這個檔案,並尋找名為 collections 的導出物件。
💡 為什麼一定要用 collections?
- 框架進入點:當你執行
npm run dev或npx astro sync時,Astro 會去檢查src/content/config.ts。它預期會看到一個export const collections,並以此來產生你之前看到的astro:content類型定義。 - 類型安全 (TypeScript):如果你改名成
const tests,Astro 就找不到你的定義,這會導致:
- 你無法使用
getCollection('products')(因為系統不知道有這個集合)。 - 你會看到你之前遇到的錯誤:
Cannot find module 'astro:content'。
- 你無法使用
💡 總結
在 Astro 的 src/content/config.ts 檔案中,export const collections 是固定語法。
⭐ const allProducts = await getCollection('products', ({ id }) => id.startsWith(lang));
這行程式碼是在 Astro 中用來從內容集合(Content Collections)中篩選特定語言內容的邏輯。
簡單來說,它是在告訴 Astro:「請幫我從 products 這個集合裡,找出所有 id 是以當前語言(lang)開頭的資料。」
💡 詳細拆解
這行程式碼由三個核心部分組成:
getCollection('products', ...)
這是 Astro 內建的函式,用來獲取你在src/content/config.ts中定義的products集合裡的所有資料。({ id }) => id.startsWith(lang)(篩選函式)
這是getCollection的第二個參數,一個過濾器(Filter)。
{ id }:這是從每一筆產品資料中解構出來的「識別碼」。在 Astro 中,如果你的檔案路徑是src/content/products/zh-tw/apple.md,那麼它的id通常就是zh-tw/apple。id.startsWith(lang):這是一個條件判斷。如果lang的值是 "zh-tw",它就會檢查該產品的 ID 是不是由 "zh-tw" 開頭。
await
因為讀取檔案內容是異步操作(Async),所以需要配合await等待資料讀取完畢。
💡 為什麼要這樣寫? (多語系應用場景)
當你在做多語系(i18n)網站時,通常會把不同語言的文章放在對應的資料夾:
src/content/products/
├── en/
│ └── iphone.md (ID: "en/iphone")
└── zh-tw/
└── iphone.md (ID: "zh-tw/iphone")
如果你現在的頁面是中文版(lang = "zh-tw"),這行程式碼就能確保 allProducts 裡面只會拿到中文的產品資料,而不會把英文版的也抓進來。
💡 舉個例子
假設 lang = "en":
"en/iphone".startsWith("en") → True (保留)"zh-tw/iphone".startsWith("en") → False (排除)
⚠️ 如果 ...getCollection... IDE 出現錯誤
const allProducts = await getCollection('products', ({ id }) =>
id.startsWith(lang)
)
❗ 'products' IDE 出現錯誤
No overload matches this call.
Overload 1 of 2, '(collection: never, filter?: ((entry: never) => entry is never) | undefined): Promise<never[]>', gave the following error.
Argument of type '"products"' is not assignable to parameter of type 'never'.
Overload 2 of 2, '(collection: never, filter?: ((entry: never) => unknown) | undefined): Promise<never[]>', gave the following error.
Argument of type '"products"' is not assignable to parameter of type 'never'.
看到 Argument of type '"products"' is not assignable to parameter of type 'never' 這個錯誤,
通常代表 TypeScript 雖然讀到了程式碼,但 Astro 沒能成功把你的 products 設定轉換成類型定義。
簡單來說:TypeScript 覺得你的內容集合(Collections)是空的,所以你給它任何名字(如 "products"),它都認為是不存在的(never)。
請按照以下順序檢查並修復:
① 檢查 src/content/config.ts 的導出
最常見的原因是 config.ts 寫法不標準。請確認你的檔案看起來像這樣:
import { defineCollection, z } from 'astro:content'
import { glob } from 'astro/loaders' // 如果是 Astro 5.0+
const products = defineCollection({
loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/products' }),
schema: z.object({
title: z.string(),
// ... 其他欄位
}),
})
// 必須叫這個名字,且必須 export
export const collections = {
products: products,
}
② 執行 sync 指令 (強制重新產生類型)
這是解決 never 錯誤最有效的方法。Astro 需要在 .astro/ 資料夾生成隱藏的類型檔案。
請在終端機執行:
npx astro sync③ 檢查檔案路徑與副檔名
如果你的 loader 設定的路徑下沒有任何檔案,Astro 有時會推斷該集合為空,導致類型變成 never。
- 確認
./src/content/products資料夾裡真的有.md或.mdx檔案。 - 確認檔案內容是否有正確的 Frontmatter(標題、日期等),且符合你定義的
schema。
④ VS Code 的快取問題
有時候檔案產生了,但 TypeScript 沒跟上:
- 開啟
src/pages/...那個報錯的檔案。 - 按
F1或Ctrl+Shift+P。 - 輸入
TypeScript: Restart TS Server並執行。
💡 為什麼會報 never 錯誤?
在 TypeScript 中,當一個聯集型別(Union Type)沒有任何選項時,它會變成 never。
- 正常情況:
collection參數應該是'products' | 'blog' | 'news'。 - 錯誤情況:因為 Astro 沒抓到
config.ts的內容,這個清單變成了空的,TypeScript 就會標示為never。










