[CODE]

用 Astro + Tailwind v4 蓋五個民宿網站的心得

同樣的技術棧,五個不同客戶,有重複出現的模式,也有每次都踩到的坑。SSG vs SSR 的選擇、響應式圖片、還有一個用 GitHub API 搭的後台。

2 min read
astro tailwind typescript web

三個月前我開始接台灣民宿的數位化工作。不是那種顧問簡報型的,而是真的下去做的那種:業主用 LINE 群組管訂房、沒有自己的網站、或者完全靠 Booking.com 抽佣。做完之後,我蓋了五個網站,涵蓋了九家民宿。

需求很一致:讓業主脫離第三方平台、給他們一個手機連線不好也能快速載入的網站、讓他們自己能更新房型和價格,不用打電話給我。技術選型就從這些限制倒推出來。

五個網站全部用 Astro 6 加上 Tailwind CSS v4。開始的時候心想第二、第三個應該大半複製貼上就好,結果不是。每個專案都有前一個沒碰過的問題。

為什麼這個使用場景適合 Astro

民宿行銷網站大部分都是讀取流量。旅客進來、瀏覽房型、看價格、決定訂不訂。需要互動的地方很少,就一個訂房表單、可能加個日期選擇器。其他全部都是不會在 request 之間改變的靜態 HTML。

Astro 的 island 架構天然符合這個需求:95% 的頁面內容出靜態 HTML,只有訂房元件才 hydrate。產出預設就快,不需要手動優化。對那些一個差的第一印象就可能失去客人的業主來說,這點很重要。

最關鍵的第一個選擇:SSG 還是 SSR?

這是五個專案裡最重要的分叉點。

lightstayshengchun 是個小民宿,房型固定、定價固定、頁尾放電話號碼,沒有預訂表單、沒有動態內容。靜態網站,部署到 Firebase Hosting,完成。靜態站不用伺服器,cold start 根本不是問題。

其他幾個就需要 server-side 功能:要能寄信的聯絡表單、會隨季節或空房狀況變動的定價。這幾個我用 @astrojs/node adapter,部署到 Railway。Astro 的 adapter 換起來蠻乾淨的,換掉 adapter 設定,其他程式碼幾乎不用動。

講實話的踩坑點:Railway 免費方案的 cold start 很明顯,閒置一段時間後第一個 request 要等幾秒。我在所有 SSR 站前面架了 Cloudflare,把能 cache 的頁面都 cache 在 edge,行銷頁這樣改善不少,表單 endpoint 就沒辦法幫到。

架構上補充一點:Cloudflare 擋在 Railway 前面,表示旅客查看房型頁幾乎都能拿到 cached 的回應。SSR 的成本只在表單送出和偶爾 cache miss 的時候才付。以這些客戶的預算規模,這個取捨可以接受。

響應式圖片比我預期的麻煩

每個客戶都給我一資料夾的高解析度 JPEG,有的單張 8MB。手機上這很吃虧——對於正在和 OTA 平台搶客人的民宿來說,圖片載入慢就是轉換率殺手。

我寫了一個腳本 convert-to-webp.mjs,吃進原始圖片資料夾,輸出三種尺寸 — 480、960、1600px — 加上 WebP 跟 AVIF 兩種格式:

import sharp from "sharp";
import { readdir } from "fs/promises";

const widths = [480, 960, 1600];
const formats = ["webp", "avif"];

for (const file of await readdir("./src/assets/raw")) {
  for (const width of widths) {
    for (const fmt of formats) {
      await sharp(`./src/assets/raw/${file}`)
        .resize(width)
        .toFormat(fmt)
        .toFile(`./public/images/${file}-${width}.${fmt}`);
    }
  }
}

然後在 Astro component 裡手動接 srcset。不算優雅,但能用。super-inn 的圖片總重從大約 14MB 壓到不到 1MB,大概 92% 的減少幅度。這種數字客戶聽得懂,反應也比較明顯。

字體載入是個意外的雷

Tailwind v4 改了字體相關的處理方式,跟 v3 不一樣,加上 Google Fonts 在幾個站上造成 first paint 被卡住。真正有效的解法是老招:media="print" swap:

<link
  rel="preconnect"
  href="https://fonts.googleapis.com"
/>
<link
  rel="stylesheet"
  href="https://fonts.googleapis.com/css2?family=Noto+Serif+TC&display=swap"
  media="print"
  onload="this.media='all'"
/>

preconnect 讓 DNS 提早解析,media="print" 讓瀏覽器不把它當成 render-blocking 資源。載入完成後 onload 把 media 換回 all。這個技巧不新,但我每次都要重新想一遍,後來乾脆直接寫進 base layout 模板。

後台管理的問題

五個案子裡有三個客戶希望自己能更新促銷 banner 和房型卡片,不想碰程式碼。這是很多小型企業數位化專案最常搞錯的地方——你給業主一個 Notion export 或 CMS 帳號,結果他們再也不登入,因為操作邏輯跟他們想事情的方式完全不一樣。

我不想為了這麼輕量的需求架 CMS 或資料庫。這些業主大多不會去登入 Contentful 後台。他們需要的是像填表單一樣的體驗,而不是管理內容的感覺。

最後用了 /astro-promo-admin skill,一個網頁後台,讓客戶透過表單編輯 banner 文案和卡片內容,送出後透過 Octokit 直接 commit 到 deploy branch。Branch 更新就觸發 CI/CD 重新 build 和部署。客戶完全不用開終端機。

代價是部署需要一兩分鐘,不是即時生效。對民宿網站這種更新季節促銷的場景,沒人在意。如果是新聞網站,這就行不通了。

更深層的一點是:在這套系統上線之前,業主要改房價得打電話給我,等一天,還要付一個小時的工時費。現在他們自己改,自己存。這個轉變——從「打電話給工程師」變成「自己編輯存檔」——才是這個專案真正交付的價值,不是 Astro 的 build。

Tailwind v4 實際上改了什麼

一開始有點緊張,因為 v3 的肌肉記憶很深。實際用起來,CSS-first 設定方式 — 把 design token 定義在 .css 檔而不是 tailwind.config.js — 大概花了一天適應,之後反而覺得比較清楚。尤其是跨頁面共用調色盤的專案,這樣管理感覺更自然。

有一個踩到的地方:某些 v3 的 utility 名稱改了或移除了。我在 ring utilities 和幾個漸層 helper 上卡到。每個問題大概二十分鐘內解決,但如果要從 v3 遷移過來,值得先查一下 changelog。

這個技術棧還會再用嗎?

對這個使用場景,會。Astro 的 island 架構讓頁面預設就很快,Tailwind v4 不需要引入 component library 也能維持視覺一致性。每個專案可以獨立選 SSG 或 SSR,部署方式很彈性。

會想改掉的一件事:那個圖片轉換腳本應該從一開始就放進共用工具 repo,而不是每個專案複製一份。到第五個站的時候,我手上有四個長得差不多但各有微差的版本在維護。

更大的收穫是:這裡的技術選擇——Astro 而不是 Next.js、GitHub API 而不是 CMS、靜態用 Firebase SSR 用 Railway——都不是在真空中做的決定。是因為這些業主需要快、便宜、他們自己能操作的網站,才選出這些工具的。技術棧跟著限制走,不是反過來。