Astro 進階課程 1. 視覺美學與 UI 框架 —— Tailwind CSS 整合-實作筆記

更新 發佈閱讀 25 分鐘

✏️ 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.

錯誤訊息分析

  1. 你有一個 元組型別或陣列型別 被定義為 [](空陣列)。
  2. 你嘗試給它賦值 [string, string](長度為 2 的陣列)。
  3. 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 或編輯器插件還停留在舊版的設定。

為什麼會報這個錯?

  1. Tailwind v4 語法@theme 是 v4 專屬的,如果你是用舊版的 Tailwind 插件或是 VS Code 的 CSS 檢查工具,它會把它當作無效的 CSS 語法。
  2. 配置衝突:目前 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 並消除錯誤,請照著以下步驟調整:

  1. 修改 astro.config.mjs: 不要使用 Vite 插件,改用 Astro 官方整合包:
import tailwind from '@astrojs/tailwind' // 先安裝 npm install @astrojs/tailwind

export default defineConfig({
integrations: [tailwind()],
// ... 移除 vite 裡的 tailwindcss 插件
})
  1. 修改 global.css: 將內容改回 v3 標準語法:
@tailwind base;
@tailwind components;
@tailwind utilities;
  1. 使用 tailwind.config.mjs: 把品牌橘色寫在裡面(如之前討論的 theme.extend)。

個人作法

我是採用 方法一,因為已經使用 v4 而且頁面使用顏色也有出來
這代表 Tailwind CSS v4 配置其實已經生效了,目前的紅字僅僅是 VS Code 的內建 CSS 檢查器不認識新語法(如 @theme 或 @import "tailwindcss")所造成的。
這是因為 VS Code 預設的 CSS 驗證引擎是基於標準 CSS 規範,而 Tailwind v4 的一些新指令對它來說像是「語法錯誤」。

如何消除 VS Code 的紅字警告?
有三種方法可以解決,建議從 方法一 開始:

  1. 安裝 Tailwind CSS IntelliSense 插件(由 Tailwind Labs 開發)。
  2. 修改 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 頁面。

總結建議

  1. 檢查 [id].astro:如果你想要繼續用 Astro.props,請務必加上 export const prerender = true;
  2. 如果你想發揮 SSR 優勢:請改用方案 A 的 getEntry('blog', id) 寫法,這樣你就算以後有 1000 篇文章,也不需要在 build 時全部生成。

📚 額外筆記

🧠 關於 npx astro add tailwind

  • 這個指令其實是 Astro 官方提供的整合器 (integration)
  • 它的功能是:
  1. 安裝 tailwindcsspostcssautoprefixer
  2. 自動修改 astro.config.mjs,加入 Tailwind integration
  3. 創建一個 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;

常見疑難排解表

raw-image

💡 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. 核心差異對照表

raw-image

4. 為什麼你之前的程式碼會 post is null?

因為你的 astro.config.mjs 設定了 output: 'server',這會讓 Astro 預設進入 SSR 模式

  1. 當進入 SSR 模式,Astro 會無視你的 getStaticPaths(除非你宣告 prerender)。
  2. 因為 getStaticPaths 沒跑,所以 Astro.props 沒有被注入資料。
  3. 結果:const { post } = Astro.props 拿到的就是 undefined (或 null)。

接下來你該怎麼選?

  • 如果你想讓部落格跑得最快:保留 getStaticPaths,但在 [id].astro 最上面加一行 export const prerender = true;
  • 如果你想學習 SSR 運作:刪除 getStaticPaths,改在 --- 區塊內寫 const post = await getEntry('blog', id);
留言
avatar-img
李昀瑾的沙龍
0會員
32內容數
李昀瑾的沙龍的其他內容
2026/01/14
只是目錄,看看就好 專題一:視覺美學與 UI 框架 —— Tailwind CSS 整合 專題二:狀態管理與跨組件通訊 —— Nano Stores 專題三:持久化資料存放 —— 整合 Supabase 或 Drizzle ORM
2026/01/14
只是目錄,看看就好 專題一:視覺美學與 UI 框架 —— Tailwind CSS 整合 專題二:狀態管理與跨組件通訊 —— Nano Stores 專題三:持久化資料存放 —— 整合 Supabase 或 Drizzle ORM
2026/01/13
Astro - 基礎入門6.第六課練習題實作
2026/01/13
Astro - 基礎入門6.第六課練習題實作
2026/01/13
第六課:中間件 (Middleware) 與生產部署 (Deployment) 核心觀念:Middleware 是什麼? 中間件是站在「請求 (Request)」與「頁面 (Page)」之間的一道關卡。每當有人點擊你的網站,中間件會先執行。這非常適合用來處理:身份驗證、重定向 ...
2026/01/13
第六課:中間件 (Middleware) 與生產部署 (Deployment) 核心觀念:Middleware 是什麼? 中間件是站在「請求 (Request)」與「頁面 (Page)」之間的一道關卡。每當有人點擊你的網站,中間件會先執行。這非常適合用來處理:身份驗證、重定向 ...
看更多
你可能也想看
Thumbnail
本文深度解析賽勒布倫尼科夫的舞臺作品《傳奇:帕拉贊諾夫的十段殘篇》,如何以十段殘篇,結合帕拉贊諾夫的電影美學、象徵意象與當代政治流亡抗爭,探討藝術在儀式消失的現代社會如何承接意義,並展現不羈的自由靈魂。
Thumbnail
本文深度解析賽勒布倫尼科夫的舞臺作品《傳奇:帕拉贊諾夫的十段殘篇》,如何以十段殘篇,結合帕拉贊諾夫的電影美學、象徵意象與當代政治流亡抗爭,探討藝術在儀式消失的現代社會如何承接意義,並展現不羈的自由靈魂。
Thumbnail
本文分析導演巴里・柯斯基(Barrie Kosky)如何運用極簡的舞臺配置,將布萊希特(Bertolt Brecht)的「疏離效果」轉化為視覺奇觀與黑色幽默,探討《三便士歌劇》在當代劇場中的新詮釋,並藉由舞臺、燈光、服裝、音樂等多方面,分析該作如何在保留批判核心的同時,觸及觀眾的觀看位置與人性幽微。
Thumbnail
本文分析導演巴里・柯斯基(Barrie Kosky)如何運用極簡的舞臺配置,將布萊希特(Bertolt Brecht)的「疏離效果」轉化為視覺奇觀與黑色幽默,探討《三便士歌劇》在當代劇場中的新詮釋,並藉由舞臺、燈光、服裝、音樂等多方面,分析該作如何在保留批判核心的同時,觸及觀眾的觀看位置與人性幽微。
Thumbnail
5 月將於臺北表演藝術中心映演的「2026 北藝嚴選」《海妲・蓋柏樂》,由臺灣劇團「晃晃跨幅町」製作,本文將以從舞台符號、聲音與表演調度切入,討論海妲・蓋柏樂在父權社會結構下的困境,並結合榮格心理學與馮.法蘭茲對「阿尼姆斯」與「永恆少年」原型的分析,理解女人何以走向精神性的操控、毀滅與死亡。
Thumbnail
5 月將於臺北表演藝術中心映演的「2026 北藝嚴選」《海妲・蓋柏樂》,由臺灣劇團「晃晃跨幅町」製作,本文將以從舞台符號、聲音與表演調度切入,討論海妲・蓋柏樂在父權社會結構下的困境,並結合榮格心理學與馮.法蘭茲對「阿尼姆斯」與「永恆少年」原型的分析,理解女人何以走向精神性的操控、毀滅與死亡。
Thumbnail
《轉轉生》(Re:INCARNATION)為奈及利亞編舞家庫德斯.奧尼奎庫與 Q 舞團創作的當代舞蹈作品,結合拉各斯街頭節奏、Afrobeat/Afrobeats、以及約魯巴宇宙觀的非線性時間,建構出關於輪迴的「誕生—死亡—重生」儀式結構。本文將從約魯巴哲學概念出發,解析其去殖民的身體政治。
Thumbnail
《轉轉生》(Re:INCARNATION)為奈及利亞編舞家庫德斯.奧尼奎庫與 Q 舞團創作的當代舞蹈作品,結合拉各斯街頭節奏、Afrobeat/Afrobeats、以及約魯巴宇宙觀的非線性時間,建構出關於輪迴的「誕生—死亡—重生」儀式結構。本文將從約魯巴哲學概念出發,解析其去殖民的身體政治。
Thumbnail
這是一場從「網路連結 → 線下見面」的活動,我一開始其實有些猶豫,畢竟地點對我來說不近,加上平常在社群裡其實不太主動互動。 但因為主辦人西打誠意滿滿地邀請,甚至還提出補貼車資,最後我決定自費參加。現在回頭看,真的很值得! 場地很有感,氛圍超溫暖 一踏進場地就被暖黃的燈光包圍,小閣樓超舒適,還
Thumbnail
這是一場從「網路連結 → 線下見面」的活動,我一開始其實有些猶豫,畢竟地點對我來說不近,加上平常在社群裡其實不太主動互動。 但因為主辦人西打誠意滿滿地邀請,甚至還提出補貼車資,最後我決定自費參加。現在回頭看,真的很值得! 場地很有感,氛圍超溫暖 一踏進場地就被暖黃的燈光包圍,小閣樓超舒適,還
Thumbnail
從實際應用中學習 Python 程式設計,提升技能並建立作品集。文章提供八個循序漸進的 Python 專案範例,涵蓋檔案操作、網路爬蟲、Web 應用、自動化腳本、數據分析、遊戲開發、API 互動及應用程式部署,並附上實戰建議及學習資源。
Thumbnail
從實際應用中學習 Python 程式設計,提升技能並建立作品集。文章提供八個循序漸進的 Python 專案範例,涵蓋檔案操作、網路爬蟲、Web 應用、自動化腳本、數據分析、遊戲開發、API 互動及應用程式部署,並附上實戰建議及學習資源。
Thumbnail
網站開發專案成功的關鍵在於與客戶的有效溝通。本文分享一個成功案例,說明如何透過明確掌握專案需求、主動提供技術方案、定期回報進度、完善技術協助及建立良好客戶關係,順利完成一個中文影片學習分享網站的建置,並獲得客戶高度滿意與後續合作機會。
Thumbnail
網站開發專案成功的關鍵在於與客戶的有效溝通。本文分享一個成功案例,說明如何透過明確掌握專案需求、主動提供技術方案、定期回報進度、完善技術協助及建立良好客戶關係,順利完成一個中文影片學習分享網站的建置,並獲得客戶高度滿意與後續合作機會。
Thumbnail
Nuxt.js 是以 Vue 為基底所建構的框架,透過 Nuxt.js,我們能夠更輕鬆地開發靜態頁面 (Static Site)、操作體驗良好的單頁式網站 (SPA)、甚至是顧及 SEO 的伺服器端渲染 (SSR) 網站。
Thumbnail
Nuxt.js 是以 Vue 為基底所建構的框架,透過 Nuxt.js,我們能夠更輕鬆地開發靜態頁面 (Static Site)、操作體驗良好的單頁式網站 (SPA)、甚至是顧及 SEO 的伺服器端渲染 (SSR) 網站。
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News