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;

常見疑難排解表

vocus|新世代的創作平台

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

vocus|新世代的創作平台

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會員
36內容數
李昀瑾的沙龍的其他內容
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
Nuxt.js 是以 Vue 為基底所建構的框架,透過 Nuxt.js,我們能夠更輕鬆地開發靜態頁面 (Static Site)、操作體驗良好的單頁式網站 (SPA)、甚至是顧及 SEO 的伺服器端渲染 (SSR) 網站。
Thumbnail
Nuxt.js 是以 Vue 為基底所建構的框架,透過 Nuxt.js,我們能夠更輕鬆地開發靜態頁面 (Static Site)、操作體驗良好的單頁式網站 (SPA)、甚至是顧及 SEO 的伺服器端渲染 (SSR) 網站。
Thumbnail
背景:從冷門配角到市場主線,算力與電力被重新定價   小P從2008進入股市,每一個時期的投資亮點都不同,記得2009蘋果手機剛上市,當時蘋果只要在媒體上提到哪一間供應鏈,隔天股價就有驚人的表現,當時光學鏡頭非常熱門,因為手機第一次搭上鏡頭可以拍照,也造就傳統相機廠的殞落,如今手機已經全面普及,題
Thumbnail
背景:從冷門配角到市場主線,算力與電力被重新定價   小P從2008進入股市,每一個時期的投資亮點都不同,記得2009蘋果手機剛上市,當時蘋果只要在媒體上提到哪一間供應鏈,隔天股價就有驚人的表現,當時光學鏡頭非常熱門,因為手機第一次搭上鏡頭可以拍照,也造就傳統相機廠的殞落,如今手機已經全面普及,題
Thumbnail
這是一場修復文化與重建精神的儀式,觀眾不需要完全看懂《遊林驚夢:巧遇Hagay》,但你能感受心與土地團聚的渴望,也不急著在此處釐清或定義什麼,但你的在場感受,就是一條線索,關於如何找著自己的路徑、自己的聲音。
Thumbnail
這是一場修復文化與重建精神的儀式,觀眾不需要完全看懂《遊林驚夢:巧遇Hagay》,但你能感受心與土地團聚的渴望,也不急著在此處釐清或定義什麼,但你的在場感受,就是一條線索,關於如何找著自己的路徑、自己的聲音。
Thumbnail
本文分析導演巴里・柯斯基(Barrie Kosky)如何運用極簡的舞臺配置,將布萊希特(Bertolt Brecht)的「疏離效果」轉化為視覺奇觀與黑色幽默,探討《三便士歌劇》在當代劇場中的新詮釋,並藉由舞臺、燈光、服裝、音樂等多方面,分析該作如何在保留批判核心的同時,觸及觀眾的觀看位置與人性幽微。
Thumbnail
本文分析導演巴里・柯斯基(Barrie Kosky)如何運用極簡的舞臺配置,將布萊希特(Bertolt Brecht)的「疏離效果」轉化為視覺奇觀與黑色幽默,探討《三便士歌劇》在當代劇場中的新詮釋,並藉由舞臺、燈光、服裝、音樂等多方面,分析該作如何在保留批判核心的同時,觸及觀眾的觀看位置與人性幽微。
Thumbnail
5 月將於臺北表演藝術中心映演的「2026 北藝嚴選」《海妲・蓋柏樂》,由臺灣劇團「晃晃跨幅町」製作,本文將以從舞台符號、聲音與表演調度切入,討論海妲・蓋柏樂在父權社會結構下的困境,並結合榮格心理學與馮.法蘭茲對「阿尼姆斯」與「永恆少年」原型的分析,理解女人何以走向精神性的操控、毀滅與死亡。
Thumbnail
5 月將於臺北表演藝術中心映演的「2026 北藝嚴選」《海妲・蓋柏樂》,由臺灣劇團「晃晃跨幅町」製作,本文將以從舞台符號、聲音與表演調度切入,討論海妲・蓋柏樂在父權社會結構下的困境,並結合榮格心理學與馮.法蘭茲對「阿尼姆斯」與「永恆少年」原型的分析,理解女人何以走向精神性的操控、毀滅與死亡。
Thumbnail
網站開發專案成功的關鍵在於與客戶的有效溝通。本文分享一個成功案例,說明如何透過明確掌握專案需求、主動提供技術方案、定期回報進度、完善技術協助及建立良好客戶關係,順利完成一個中文影片學習分享網站的建置,並獲得客戶高度滿意與後續合作機會。
Thumbnail
網站開發專案成功的關鍵在於與客戶的有效溝通。本文分享一個成功案例,說明如何透過明確掌握專案需求、主動提供技術方案、定期回報進度、完善技術協助及建立良好客戶關係,順利完成一個中文影片學習分享網站的建置,並獲得客戶高度滿意與後續合作機會。
Thumbnail
從實際應用中學習 Python 程式設計,提升技能並建立作品集。文章提供八個循序漸進的 Python 專案範例,涵蓋檔案操作、網路爬蟲、Web 應用、自動化腳本、數據分析、遊戲開發、API 互動及應用程式部署,並附上實戰建議及學習資源。
Thumbnail
從實際應用中學習 Python 程式設計,提升技能並建立作品集。文章提供八個循序漸進的 Python 專案範例,涵蓋檔案操作、網路爬蟲、Web 應用、自動化腳本、數據分析、遊戲開發、API 互動及應用程式部署,並附上實戰建議及學習資源。
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News