Astro - 基礎入門5. 如何讓使用者與你的伺服器通訊

更新 發佈閱讀 20 分鐘

第五課:Astro Actions —— 安全、簡單的伺服器通訊

核心觀念:Actions 是什麼?

前你需要寫一個 src/pages/api/login.ts,然後在前端寫 fetch('/api/login', { method: 'POST', ... })

在 Astro 5 中,Action 就像是一個可以被前端直接呼叫的「伺服器函式」。它具備:

  1. 自動型別檢查:前端知道後端需要什麼資料。
  2. 安全:直接在伺服器端執行。
  3. 簡單:不需要處理 URL 和 JSON 轉換。

1. 手把手實作:定義你的第一個 Action

請建立檔案 src/actions/index.ts

import { defineAction } from 'astro:actions'
import { z } from 'astro:schema'

export const server = {
// 定義一個名為 getGreeting 的動作
getGreeting: defineAction({
input: z.object({
name: z.string(),
}),
handler: async (input) => {
// 這裡可以寫資料庫操作,例如:db.user.create(...)
return `你好 ${input.name},這是來自伺服器的回覆!`
},
}),
}

2. 手把手實作:在前端呼叫 Action

修改你的 src/pages/index.astro,我們來做一個簡單的聯絡表單:

---
import BaseLayout from '../layouts/BaseLayout.astro';
---

<BaseLayout pageTitle="Astro Actions 實驗">
<h2>與伺服器對話</h2>

<form id="greeting-form">
<input type="text" name="name" placeholder="輸入你的名字" required />
<button type="submit">發送</button>
</form>

<p id="result"></p>

<script>
import { actions } from 'astro:actions';

const form = document.querySelector('form');
const resultElement = document.querySelector('#result');

form?.addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(form);
const name = formData.get('name') as string;

// 直接呼叫伺服器函式,像呼叫普通 function 一樣!
const { data, error } = await actions.getGreeting({ name });

if (!error) {
resultElement!.textContent = data;
} else {
resultElement!.textContent = "出錯了:" + error.message;
}
});
</script>
</BaseLayout>

📝 第五課練習題

任務目標:實作一個「留言板」功能。

  1. 在 src/actions/index.ts 中,增加一個新的 Action 叫做 postComment
  2. 這個 Action 需要接收 author (字串) 和 content (字串)。
  3. 在 handler 中,將收到的資料 console.log 出來(模擬存入資料庫),並回傳「留言成功」。
  4. 在 about.astro 建立一個簡單的表單,讓使用者輸入名字與內容,並在提交後用 alert() 顯示伺服器的回傳訊息。
  5. 進階挑戰: 嘗試在 Action 的 input 中使用 z.string().min(5),看看如果你輸入少於 5 個字的內容,前端會收到什麼樣的錯誤訊息?

第五課練習題實作放在下一篇 ( 我也是第一次學習,自己試作,不一定是對的喔👍 )


📚 額外筆記

※ resultElement!.textContent = data;

這個 ! 不是邏輯 NOT,而是 TypeScript 的「非空斷言(Non-null Assertion)」。👉 TypeScript 保證:resultElement 絕對不是 null 或 undefined

為什麼會需要 !

假設你前面是這樣拿 DOM:

const resultElement = document.querySelector('#result');

TypeScript 看到這行,會推斷型別是:

Element | null

因為:

  • 找得到 → Element
  • 找不到 → null

所以你直接寫:

resultElement.textContent = data

❌ TypeScript 會報錯:

Object is possibly 'null'.

! 在這裡做了什麼?

resultElement!.textContent = data

等於在跟 TS 說:

「你不用再檢查了,我確定它存在」

TypeScript 就會放行,不再報錯。

⚠️ 注意:這只是「讓編譯器不再提示」,執行期不會幫你檢查

等價寫法(比較安全)

✅ 寫法 1:if 判斷(最安全)

if (resultElement) {
resultElement.textContent = data
}

✅ 寫法 2:可選鏈(不會噴錯)

resultElement?.textContent = data

找不到元素時,這行什麼都不做。

⚠️ 寫法 3:非空斷言(你現在這個)

resultElement!.textContent = data

如果 DOM 不存在,會在 runtime 直接噴錯

Cannot set properties of null

什麼時候「可以」用 !

✅ 合理情境:

// HTML 一定有
<div id="result"></div>
const resultElement = document.querySelector('#result')!;
resultElement.textContent = data

例如:

  • 你控制 HTML 結構
  • DOM 在 script 執行前就存在
  • 不可能被刪除

什麼時候「不該」用?

❌ 不確定元素是否存在
❌ 動態產生 / 延遲載入的 DOM
❌ API 回傳的資料

常見誤解 ❌

!value   // ❌ 這是 JS 的 NOT
value! // ✅ 這是 TS 的非空斷言

位置不同,意思完全不一樣。


※ import { actions } from 'astro:actions'

是在 Astro 4.x(含)之後 用來存取 Astro Actions(伺服器動作) 的核心 API。

astro:actions 讓你在前端「直接呼叫後端函式」
不用自己寫 /api/*.ts、不用手動 fetch

actions 是什麼?
actions 是一個 型別安全(type-safe)的物件
裡面包含你在 src/actions/** 定義的所有「伺服器動作」。

概念對照

raw-image

最基本範例

1️⃣ 定義一個 Action(後端)

// src/actions/serverTime.ts
import { defineAction } from 'astro:actions'

export const serverTime = defineAction({
handler: async () => {
return new Date().toISOString()
},
})

2️⃣ 在前端使用

import { actions } from 'astro:actions'

const result = await actions.serverTime()
console.log(result)

👉 這段是在瀏覽器跑,但實際執行在伺服器

背後實際發生什麼事?

// 當你呼叫:
actions.serverTime()

Astro 會自動幫你做:

  1. 建立隱藏 API endpoint
  2. 自動送 request
  3. 執行 server handler
  4. 回傳結果
  5. 保證型別正確

你完全不用碰 fetch

為什麼要用 Astro Actions?

✅ 優點

  • 🔐 預設 Server-only
  • 🧠 TypeScript 自動推斷
  • 📦 不暴露實作細節
  • 🧼 程式碼乾淨

常見用途

  • 表單送出
  • 登入 / 註冊
  • 存資料庫
  • 呼叫第三方 API
  • 權限驗證

與 server:defer 的關係

raw-image

➡️ 顯示用 defer,動作用 actions

Action + 表單範例(超常見)

---
import { actions } from 'astro:actions'
---

<form
onSubmit={async (e) => {
e.preventDefault()
const res = await actions.login({
email: 'test@test.com',
password: '123456',
})
console.log(res)
}}
>
<button>登入</button>
</form>

跟 API Routes 的差別

raw-image

※ actions 怎麼取得自訂 api ?

// src/actions/serverTime.ts
import { defineAction } from 'astro:actions'

export const serverTime = defineAction({
handler: async () => {
return new Date().toISOString()
},
})

// src/pages/index.astro
import { actions } from 'astro:actions'
actions.serverTime()
// src/actions/index.ts
export const server = {
getGreeting: defineAction({
input: z.object({
name: z.string(),
}),
handler: async (input) => {
return `你好 ${input.name},這是來自伺服器的回覆!`
},
}),
postComment: defineAction({
input: z.object({
author: z.string(),
content: z.string().min(5),
}),
handler: async (input) => {
console.log(input)
return '留言成功'
},
}),
}

// src/pages/index.astro
import { actions } from 'astro:actions'
actions.getGreeting()
actions.postComment()

不同的 api 包裝方式,卻都能在 actions 直接取得 ?

Astro Actions 的「自動展開機制」核心設計

actions 不是匯出你寫的變數名稱
而是 Astro 在 build 時,掃描所有 defineAction,把它們「攤平(flatten)」成一個 actions 物件
所以:

export const server = {
getGreeting: defineAction(...),
postComment: defineAction(...),
}

👇 在前端會變成:

actions.getGreeting()
actions.postComment()

為什麼不是 actions.server.getGreeting()

因為 server 只是你在檔案裡用來分組的普通物件名稱
Astro 不把它當成命名空間

👉 Astro 只在乎兩件事:

  1. 哪些東西是 defineAction(...)
  2. 它們的 key 名稱

Astro 實際做了什麼?

Astro 在啟動時會:

掃描 src/actions/**/*.ts

找到所有 defineAction

取「屬性名稱」當 action 名

全部掛到 actions 物件底下

等價於 Astro 內部幫你做了這件事(概念):

actions = {
getGreeting,
postComment,
serverTime,
...
}

不管你中間包了幾層物件。

為什麼要這樣設計?(設計理由)

1️⃣ 前端呼叫要「扁平、好用」

actions.getGreeting()
// 比
actions.server.getGreeting()
actions.user.comment.post()
// 簡單太多。

2️⃣ Actions 本質是「API 端點」

API endpoint 天生就是「全域名稱」

POST /_actions/getGreeting
POST /_actions/postComment

不是巢狀物件。

3️⃣ TS 型別自動推斷更容易

扁平結構:

actions.xxx()

👉 TS inference 最穩定
👉 不會有動態 key 問題

那「server」這個物件有沒有用?

有,但只是給你自己看、自己整理用。
你也可以這樣寫(完全一樣)

export const getGreeting = defineAction(...)
export const postComment = defineAction(...)
// 或
export const commentActions = {
postComment: defineAction(...),
}
// 👉 前端 都一樣:
actions.postComment()

⚠️ 重要限制

❌ Action 名稱不能衝突

// file A
export const server = {
hello: defineAction(...)
}

// file B
export const user = {
hello: defineAction(...)
}

❌ 會衝突
👉 兩個都變成 actions.hello

※ 大型專案 Astro Actions 命名規範

🎯 設計目標

  1. 避免 action 名稱衝突
  2. 一眼就知道「做什麼 + 對誰」
  3. 前端好 autocomplete
  4. 檔案怎麼拆都不影響呼叫方式

✅ 核心原則

Action 名稱必須是「全域唯一」

因為最後都會變成:

actions.xxx()

🧠 推薦命名公式

<Domain><Action><Target>
或(更清楚):
<Domain><Verb><Object>

🧩 常用 Domain(第一段)

raw-image

🛠 動詞(第二段)

raw-image

📦 物件(第三段)

raw-image

✅ 好範例

actions.userGetProfile()
actions.userUpdateProfile()
actions.postCreatePost()
actions.commentDeleteComment()
actions.authLogin()
actions.authLogout()

❌ 壞範例

actions.get()
actions.create()
actions.submit()
actions.send()
actions.save()

📁 檔案結構怎麼拆?

📁 推薦結構

src/actions/
├─ user.actions.ts
├─ auth.actions.ts
├─ post.actions.ts
├─ comment.actions.ts
└─ admin.actions.ts

user.actions.ts

export const userActions = {
userGetProfile: defineAction(...),
userUpdateProfile: defineAction(...),
}

comment.actions.ts

export const commentActions = {
commentCreateComment: defineAction(...),
commentDeleteComment: defineAction(...),
}

前端一律這樣用

import { actions } from 'astro:actions'

await actions.commentCreateComment()

🔐 Auth / Admin 特別規範

Auth 類(建議特殊動詞)

authLogin
authLogout
authRefreshToken
authValidateSession

Admin 類(強制 Admin 開頭)

adminGetUsers
adminDeleteUser
adminBanUser




留言
avatar-img
李昀瑾的沙龍
0會員
25內容數
李昀瑾的沙龍的其他內容
2026/01/11
Astro - 基礎入門4.第四課練習題實作
2026/01/11
Astro - 基礎入門4.第四課練習題實作
2026/01/11
第四課:Server Islands 與 SSR (伺服器端渲染) 在之前的課程中,我們的網站是「靜態」的。 但在真實應用中,有些內容不能是靜態的(例如:用戶登入狀態、即時庫存、或是根據地理位置推薦的內容)。 1. 核心觀念:Server Islands vs. Client Islands
2026/01/11
第四課:Server Islands 與 SSR (伺服器端渲染) 在之前的課程中,我們的網站是「靜態」的。 但在真實應用中,有些內容不能是靜態的(例如:用戶登入狀態、即時庫存、或是根據地理位置推薦的內容)。 1. 核心觀念:Server Islands vs. Client Islands
2026/01/10
第三課:Astro Islands —— 混合開發與互動性 核心觀念:預設 0 JS Astro 就像一個「大洋」,預設所有組件都是靜態的 HTML(沒有 JS)。如果你需要互動(如:彈窗、購物車、即時搜尋),你就在大洋中丟入一個「孤島 (Island)」。
2026/01/10
第三課:Astro Islands —— 混合開發與互動性 核心觀念:預設 0 JS Astro 就像一個「大洋」,預設所有組件都是靜態的 HTML(沒有 JS)。如果你需要互動(如:彈窗、購物車、即時搜尋),你就在大洋中丟入一個「孤島 (Island)」。
看更多
你可能也想看
Thumbnail
在 vocus 與你一起探索內容、發掘靈感的路上,我們又將啟動新的冒險——vocus App 正式推出! 現在起,你可以在 iOS App Store 下載全新上架的 vocus App。 無論是在通勤路上、日常空檔,或一天結束後的放鬆時刻,都能自在沈浸在內容宇宙中。
Thumbnail
在 vocus 與你一起探索內容、發掘靈感的路上,我們又將啟動新的冒險——vocus App 正式推出! 現在起,你可以在 iOS App Store 下載全新上架的 vocus App。 無論是在通勤路上、日常空檔,或一天結束後的放鬆時刻,都能自在沈浸在內容宇宙中。
Thumbnail
市場經驗拉長之後,很多投資人都會遇到同一個問題:不是方向看錯,而是部位太集中個股,常常跟大趨勢脫節。 早年的台股環境,中小股非常吃香,反而權值股不動,但QE量化寬鬆後,特別是疫情之後,後疫情時代,鈔票大量在股市走動,這些大資金只能往權值股走,因此早年小P的策略偏向中小型個股,但近年AI興起,高技術
Thumbnail
市場經驗拉長之後,很多投資人都會遇到同一個問題:不是方向看錯,而是部位太集中個股,常常跟大趨勢脫節。 早年的台股環境,中小股非常吃香,反而權值股不動,但QE量化寬鬆後,特別是疫情之後,後疫情時代,鈔票大量在股市走動,這些大資金只能往權值股走,因此早年小P的策略偏向中小型個股,但近年AI興起,高技術
Thumbnail
vocus 慶祝推出 App,舉辦 2026 全站慶。推出精選內容與數位商品折扣,訂單免費與紅包抽獎、新註冊會員專屬活動、Boba Boost 贊助抽紅包,以及全站徵文,並邀請你一起來回顧過去的一年, vocus 與創作者共同留下了哪些精彩創作。
Thumbnail
vocus 慶祝推出 App,舉辦 2026 全站慶。推出精選內容與數位商品折扣,訂單免費與紅包抽獎、新註冊會員專屬活動、Boba Boost 贊助抽紅包,以及全站徵文,並邀請你一起來回顧過去的一年, vocus 與創作者共同留下了哪些精彩創作。
Thumbnail
PRD 老是寫不完?這篇用實戰範例與 WBS、驗收標準等技巧,教你如何面對變動需求,也能做出全團隊都能對齊的好文件。
Thumbnail
PRD 老是寫不完?這篇用實戰範例與 WBS、驗收標準等技巧,教你如何面對變動需求,也能做出全團隊都能對齊的好文件。
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