✏️ 1. 利用前一章節建立的 Card.astro 組件。
① blog.astro 匯入 Card.astro
---
import BaseLayout from '../layouts/BaseLayout.astro'
import PostsQuote from '../components/PostsQuote.astro'
import Card from '../components/Card.astro'
const pageTitle = '我的部落格'
---
<BaseLayout pageTitle={pageTitle}>
<PostsQuote server:defer>
<p slot="fallback" class="loading">文章列表載入中...</p>
</PostsQuote>
<Card /> <!-- 👈 加入 -->
</BaseLayout>
當直接加入 <Card /> 時編譯器會出現錯誤:
❌Type '{}' is not assignable to type 'IntrinsicAttributes & Props'.
Type '{}' is missing the following properties from type 'Props': title, description, tags
這個錯誤是 TypeScript 常見的型別錯誤,意思是你在使用某個元件時 沒有傳入必須的 props。
我們一步步來拆解:
Props代表你元件預期的 props 型別。{}表示你實際上傳給元件的 props 是空物件。- 錯誤訊息指出:
title、description、tags這三個屬性是必須的,但你沒傳。
正確做法
1. 傳入必要的 props:
<Card
title="我的文章標題"
description="這是一篇文章描述"
tags={['React', 'TypeScript']}
/>
2. 如果想讓 props 可選,你可以修改 Props 型別:
interface Props {
title?: string
description?: string
tags?: string[]
}
注意:這樣做的話,元件內要處理可能是 undefined 的情況。② Card.astro 填入參數還是錯誤
src/pages/blog.astro
...(省略)
<BaseLayout pageTitle={pageTitle}>
<PostsQuote server:defer>
<p slot="fallback" class="loading">文章列表載入中...</p>
</PostsQuote>
<!-- 👇 Card 已設定必填參數,tags 出現錯誤訊息 -->
<Card
title="卡片標題"
description="卡片描述"
tags={['React', 'TypeScript']}
/>
</BaseLayout>
tags 出現錯誤:
❌ Type '[string, string]' is not assignable to type '[]'.
Source has 2 element(s) but target allows only 0.
錯誤訊息分析
- 你有一個 元組型別或陣列型別 被定義為 [](空陣列)。
- 你嘗試給它賦值
[string, string](長度為 2 的陣列)。 - TypeScript 認為
[]只能是 長度為 0 的陣列,所以兩者不相容。
實際狀況
原因在於 blog.astro 使用 Card 元件並設定 title 為 ['React', 'TypeScript'] ➡️ 字串陣列
但 Card.astro 定義 Props :
interface Props {
title: string
description: string
tags: []
}
interface Props 裡的 tags 宣告型別為 必須是長度為 0 的空陣列 ,而不是 字串陣列
編譯器認為這個是 ❌ 型別不符
解決方法
明確定義 tags 為 字串陣列
// Card.astro
interface Props {
title: string
description: string
tags: string[] // 👈 [] 修改為 string[]
}
✏️ 2. 將 blog.astro 的文章列表改成 Grid 網格佈局
提示:使用
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6")
src/pages/blog.astro
---
import BaseLayout from '../layouts/BaseLayout.astro'
import PostsQuote from '../components/PostsQuote.astro'
import Card from '../components/Card.astro'
import { getCollection } from 'astro:content'
import type { CollectionEntry } from 'astro:content'
const pageTitle = '我的部落格'
const posts: CollectionEntry<'blog'>[] = await getCollection('blog')
---
<BaseLayout pageTitle={pageTitle}>
<PostsQuote server:defer>
<p slot="fallback" class="loading">文章列表載入中...</p>
</PostsQuote>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{
posts
.sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf())
.map((post) => (
<Card
title={post.data.title}
description={post.data.description}
tags={post.data.tags}
/>
))
}
</div>
</BaseLayout>
✏️ 3. BaseLayout 加入一個漂亮的導覽列
背景使用半透明模糊效果 (
backdrop-blur-md bg-white/70)。src/layouts/BaseLayout.astro
...(省略)
<nav class="backdrop-blur-md bg-white/70">
<a href="/">首頁</a>
<a href="/about">關於我</a>
<a href="/blog">部落格</a>
<a href="/admin" id="navAdminHref">後臺管理</a>
</nav>
...(省略)
✏️ 4. 挑戰題:定義專屬的品牌顏色
嘗試在
tailwind.config.mjs中定義一個你專屬的品牌顏色(例如:brand-orange: '#ff5d01'),並在頁面中使用text-brand-orange。
目前專案是使用 Tailwind v4(CSS 方式)
在 v4 中,我們更推薦直接在 CSS 檔案中定義:
/* src/styles/global.css */
@import 'tailwindcss';
@theme {
--color-brand-orange: #ff5d01;
}
此時編譯器 (vscode) 可能會發生錯誤
❌ Unknown at rule @theme這個錯誤表示你的開發環境(VS Code 或 PostCSS 插件)還不認識 Tailwind CSS v4 的新語法。
這通常是因為你的 global.css 正在使用 Tailwind v4 的寫法(@theme),但你的 astro.config.mjs 或編輯器插件還停留在舊版的設定。
為什麼會報這個錯?
- Tailwind v4 語法:
@theme是 v4 專屬的,如果你是用舊版的 Tailwind 插件或是 VS Code 的 CSS 檢查工具,它會把它當作無效的 CSS 語法。 - 配置衝突:目前
astro.config.mjs裡用了import tailwindcss from '@tailwindcss/vite',這說明你正在邁向 v4,但你的 VS Code 可能還在用舊的引擎檢查代碼。
如何解決?
方法一:在 CSS 檔案中標註 (消除 VS Code 警告)
在你的 global.css 最上方加入這一行,告訴檢查工具這是一個符合標準的 CSS 檔案:
@import 'tailwindcss';
@theme {
--color-brand-orange: #ff5d01;
}
方法二:回歸 v3 配置 (最穩定的做法)
如果你想繼續使用 tailwind.config.mjs 並消除錯誤,請照著以下步驟調整:
- 修改
astro.config.mjs: 不要使用 Vite 插件,改用 Astro 官方整合包:
import tailwind from '@astrojs/tailwind' // 先安裝 npm install @astrojs/tailwind
export default defineConfig({
integrations: [tailwind()],
// ... 移除 vite 裡的 tailwindcss 插件
})
- 修改
global.css: 將內容改回 v3 標準語法:
@tailwind base;
@tailwind components;
@tailwind utilities;
- 使用
tailwind.config.mjs: 把品牌橘色寫在裡面(如之前討論的theme.extend)。
個人作法
我是採用 方法一,因為已經使用 v4 而且頁面使用顏色也有出來
這代表 Tailwind CSS v4 配置其實已經生效了,目前的紅字僅僅是 VS Code 的內建 CSS 檢查器不認識新語法(如 @theme 或 @import "tailwindcss")所造成的。
這是因為 VS Code 預設的 CSS 驗證引擎是基於標準 CSS 規範,而 Tailwind v4 的一些新指令對它來說像是「語法錯誤」。
如何消除 VS Code 的紅字警告?
有三種方法可以解決,建議從 方法一 開始:
- 安裝 Tailwind CSS IntelliSense 插件(由 Tailwind Labs 開發)。
- 修改 VS Code 設定 (最有效的方法)
將.css檔案的關聯從「CSS」改為「Tailwind CSS」,這樣 VS Code 就會讓 Tailwind 插件接管檢查,而不是用內建的引擎。
2.1 在 VS Code 中按下Ctrl + Shift + P(Mac 為Cmd + Shift + P)。
2.2 輸入Preferences: Open User Settings (JSON)並開啟。
2.3 在 JSON 檔案中加入以下配置:"files.associations": {
"*.css": "tailwindcss"
}
這樣設定就沒問題了 👌
確認你的 @theme 設定
在 v4 中,如果你自定義了變數,Tailwind 會自動幫你生成對應的類名。
@theme {
/* 這會自動生成 text-brand-orange, bg-brand-orange, border-brand-orange */
--color-brand-orange: #ff5d01;
}
5. ⚠️ 點擊 blog.astro 中的文章,post is null
還記得在 Astro - 基礎入門 6. 性能優化與部署 (Middleware & Deployment) ,安裝 vercel 後設定 astro.config.mjs 為 SSR 模式:
output: 'server',
① 核心問題:SSR 模式下 getStaticPaths 會失效
當你設定 output: 'server' 時,src/pages/posts/[id].astro 預設會變成動態路由。
除非你明確導出 export const prerender = true;,否則 Astro 不會執行 getStaticPaths。
這就是為什麼 post 一直是 null:
因為在 SSR 模式下,Astro.props 是空的,你必須改用 getEntry 透過 Astro.params 去抓資料。
② 修正方案 A:維持 SSR 模式(推薦)
如果你需要 Middleware 或是動態功能,請修改 [id].astro 如下:
---
// src/pages/posts/[id].astro
import { getEntry, render } from 'astro:content';
import BaseLayout from '../../layouts/BaseLayout.astro';
// SSR 模式下不需要 getStaticPaths (除非你想預渲染特定頁面)
const { id } = Astro.params;
// 1. 根據網址的 id 去抓取內容
if (!id) return Astro.redirect('/404');
const post = await getEntry('blog', id);
// 2. 檢查 post 是否存在
if (!post) {
return Astro.redirect('/404');
}
// 3. 渲染
const { Content } = await render(post);
---
<BaseLayout pageTitle={post.data.title}>
<h1>{post.data.title}</h1>
<Content />
</BaseLayout>
③ 修正方案 B:將此頁面改為預渲染(混合模式)
(我是採用這個方法)
---
// src/pages/posts/[id].astro
import { getCollection, render } from 'astro:content';
export const prerender = true; // <--- 加入這行!
export const getStaticPaths = async () => {
const posts = await getCollection('blog');
return posts.map((post) => ({
params: { id: post.id },
props: { post },
}));
};
const { post } = Astro.props;
// ...其餘代碼不變
---
④ 為什麼會有 [302]?
因為在 Vercel SSR 模式下,如果路由匹配失敗或 Props 遺失,Astro 或 Vercel 的底層常會嘗試進行重定向(Redirect)到首頁或 404 頁面。
總結建議
- 檢查
[id].astro:如果你想要繼續用Astro.props,請務必加上export const prerender = true;。 - 如果你想發揮 SSR 優勢:請改用方案 A 的
getEntry('blog', id)寫法,這樣你就算以後有 1000 篇文章,也不需要在 build 時全部生成。
📚 額外筆記
🧠 關於 npx astro add tailwind
- 這個指令其實是 Astro 官方提供的整合器 (integration)。
- 它的功能是:
- 安裝
tailwindcss,postcss,autoprefixer - 自動修改
astro.config.mjs,加入 Tailwind integration - 創建一個
src/styles/global.css並引入 Tailwind 基礎樣式
- 它並不一定會自動生成
tailwind.config.mjs,除非偵測到你的專案裡還沒有 Tailwind 配置文件。
💡 重點提示
- Astro 的 Tailwind integration 不強制生成 config。
- 若你需要修改 Tailwind 預設設定(例如 color, spacing),就必須手動生成
tailwind.config.mjs。
❓ tailwind 沒作用
這通常是因為 Tailwind CSS v4 的設定方式與舊版(v3)有所不同。在 Astro 中使用 npx astro add tailwind 時,有幾個關鍵點需要檢查,特別是當你使用新版的 @import 'tailwindcss'; 語法時。
1. 檢查 astro.config.mjs
確保你的配置文件正確導入了插件。如果是 v4 版本,通常會使用 Vite 的插件模式。
import { defineConfig } from 'astro/config'
import tailwindv4 from '@tailwindcss/vite' // 確保是 vite 插件版本
export default defineConfig({
vite: {
plugins: [tailwindv4()],
},
})
2. 確認 CSS 檔案是否有導入到頁面
在 Astro 中,單純寫好 global.css 是不夠的,你必須在你的 Layout 或 Page的 Frontmatter 中手動導入它。
檢查你的 src/layouts/Layout.astro 或 src/pages/index.astro:
---
// 必須要有這一行!
import '../styles/global.css';
---
<!doctype html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8" />
<title>Astro Tailwind</title>
</head>
<body>
<h1 class="text-3xl font-bold text-red-500">
如果看到紅色,代表成功了!
</h1>
</body>
</html>
3. 確認版本與依賴
如果你使用的是 Tailwind CSS v4,原本的 tailwind.config.mjs 檔案已經不再是必需品,所有的設定都應該直接在 CSS 檔案中處理。
請確認你的 package.json 中的版本:
- 如果
tailwindcss是^4.0,請確保你安裝的是@tailwindcss/vite。 - 如果是舊版 v3,你的
global.css內容應該是:
@tailwind base;
@tailwind components;
@tailwind utilities;
常見疑難排解表

💡 getEntry 和 getStaticPath 是什麼,差在哪裡?
簡單來說:getStaticPaths 是「點菜單」,而 getEntr 是「現點現做」。
1. getStaticPaths (靜態預渲染)
這是 SSG (Static Site Generation) 的核心。
- 它的行為:在網站 Build (打包) 的時候,Astro 會問這支檔案:「你打算生成哪些網址?」你會回傳一個清單(params),Astro 接著會把每一篇文章都直接印成
.html檔案。 - 資料傳遞:資料透過
props傳遞。因為在打包時資料已經抓好了,瀏覽器訪問時不需要再查資料庫。 - 適用場景:部落格、文檔網站(內容不常變動)。
- 必要條件:在
output: 'server'模式下,必須加上export const prerender = true;才會生效。
2. getEntry (動態抓取)
這是 SSR (Server-Side Rendering) 的核心。
- 它的行為:在網站 Runtime (運行時),當有人輸入網址訪問時,伺服器才會即時執行這段程式碼,根據網址上的
id去內容池裡「撈」出對應的文章。 - 資料傳遞:資料透過
Astro.params取得 ID 後,手動調用getEntry抓取。 - 適用場景:會員系統、需要即時內容的網站、或是文章數量極大(數萬篇)不想在 build 時等太久的網站。
3. 核心差異對照表

4. 為什麼你之前的程式碼會 post is null?
因為你的 astro.config.mjs 設定了 output: 'server',這會讓 Astro 預設進入 SSR 模式。
- 當進入 SSR 模式,Astro 會無視你的
getStaticPaths(除非你宣告prerender)。 - 因為
getStaticPaths沒跑,所以Astro.props沒有被注入資料。 - 結果:
const { post } = Astro.props拿到的就是undefined(或 null)。
接下來你該怎麼選?
- 如果你想讓部落格跑得最快:保留
getStaticPaths,但在[id].astro最上面加一行export const prerender = true;。 - 如果你想學習 SSR 運作:刪除
getStaticPaths,改在---區塊內寫const post = await getEntry('blog', id);。










