vocus logo

方格子 vocus

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會員
32內容數
李昀瑾的沙龍的其他內容
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
本文深度解析賽勒布倫尼科夫的舞臺作品《傳奇:帕拉贊諾夫的十段殘篇》,如何以十段殘篇,結合帕拉贊諾夫的電影美學、象徵意象與當代政治流亡抗爭,探討藝術在儀式消失的現代社會如何承接意義,並展現不羈的自由靈魂。
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
PRD 老是寫不完?這篇用實戰範例與 WBS、驗收標準等技巧,教你如何面對變動需求,也能做出全團隊都能對齊的好文件。
Thumbnail
PRD 老是寫不完?這篇用實戰範例與 WBS、驗收標準等技巧,教你如何面對變動需求,也能做出全團隊都能對齊的好文件。
Thumbnail
這是一場從「網路連結 → 線下見面」的活動,我一開始其實有些猶豫,畢竟地點對我來說不近,加上平常在社群裡其實不太主動互動。 但因為主辦人西打誠意滿滿地邀請,甚至還提出補貼車資,最後我決定自費參加。現在回頭看,真的很值得! 場地很有感,氛圍超溫暖 一踏進場地就被暖黃的燈光包圍,小閣樓超舒適,還
Thumbnail
這是一場從「網路連結 → 線下見面」的活動,我一開始其實有些猶豫,畢竟地點對我來說不近,加上平常在社群裡其實不太主動互動。 但因為主辦人西打誠意滿滿地邀請,甚至還提出補貼車資,最後我決定自費參加。現在回頭看,真的很值得! 場地很有感,氛圍超溫暖 一踏進場地就被暖黃的燈光包圍,小閣樓超舒適,還
Thumbnail
從實際應用中學習 Python 程式設計,提升技能並建立作品集。文章提供八個循序漸進的 Python 專案範例,涵蓋檔案操作、網路爬蟲、Web 應用、自動化腳本、數據分析、遊戲開發、API 互動及應用程式部署,並附上實戰建議及學習資源。
Thumbnail
從實際應用中學習 Python 程式設計,提升技能並建立作品集。文章提供八個循序漸進的 Python 專案範例,涵蓋檔案操作、網路爬蟲、Web 應用、自動化腳本、數據分析、遊戲開發、API 互動及應用程式部署,並附上實戰建議及學習資源。
Thumbnail
網站開發專案成功的關鍵在於與客戶的有效溝通。本文分享一個成功案例,說明如何透過明確掌握專案需求、主動提供技術方案、定期回報進度、完善技術協助及建立良好客戶關係,順利完成一個中文影片學習分享網站的建置,並獲得客戶高度滿意與後續合作機會。
Thumbnail
網站開發專案成功的關鍵在於與客戶的有效溝通。本文分享一個成功案例,說明如何透過明確掌握專案需求、主動提供技術方案、定期回報進度、完善技術協助及建立良好客戶關係,順利完成一個中文影片學習分享網站的建置,並獲得客戶高度滿意與後續合作機會。
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News