【App 工程師的前端轉生術 08】前端也能寫後端:BFF 模式與 Server Actions
01. 前言:為什麼要自己寫後端?
做 App 開發時,我們常處於被動狀態:
- 後端給什麼 JSON,我們就得吞下去。
- 後端欄位命名很爛 (
user_namevsusername),我們得寫 Mapper。 - 首頁需要顯示「使用者資訊」+「最新消息」+「廣告」,後端卻叫你打三支 API 自己組裝。
這導致 App 端的 ViewModel 變得肥大,充滿了與 UI 無關的資料處理邏輯。
在 Next.js,你擁有了 Node.js Server 的能力。你可以(也應該)在前端與真實後端之間,架設一層 BFF (Backend for Frontend)。
簡單來說:你自己寫一個中介層,把資料整理成 UI 最舒服的樣子,再丟給 React。
02. API Routes:你的專屬轉接頭 (Adapter)
Next.js 的 app/api/ 目錄允許你編寫後端 API。這就像是你自己在雲端跑了一個輕量級的後端服務。
情境:首頁需要 User 和 News,但來源是兩個不同的微服務。
傳統 App 作法: App 發出兩個 Request -> 等待兩個都回來 -> 在 ViewModel 合併 -> 渲染。 (Latency 是 2x)
Next.js BFF 作法:
建立一個聚合 API app/api/home-feed/route.ts:
import { NextResponse } from 'next/server';
import { apiClient } from '@/infrastructure/api/apiClient';
// 這是在伺服器端執行的 (Node.js)
export async function GET() {
// 1. 平行請求多個後端服務 (Server-to-Server 速度極快)
const [userRes, newsRes] = await Promise.all([
apiClient.get('[https://service-user.com/me](https://service-user.com/me)'),
apiClient.get('[https://service-news.com/latest](https://service-news.com/latest)')
]);
// 2. 資料清洗 (Data Transformation)
// 把後端那些噁心的欄位名改成 UI 友善的格式
const viewModel = {
greeting: `Hello, ${userRes.full_name}`, // 這裡組字串,UI 就不用組
articles: newsRes.items.map((item: any) => ({
id: item.article_id,
title: item.subject,
image: item.cover_url || '/placeholder.png', // 處理預設值
}))
};
// 3. 回傳乾淨的 JSON 給前端
return NextResponse.json(viewModel);
}
App 工程師視角:
這就像是你把 RxJava.zip() 或 DispatchGroup 的邏輯移到了伺服器上做。
好處?手機省電、省流量(只傳需要的欄位)、且不暴露真實後端的 API Key。
03. Server Actions:呼叫函式即 API (RPC)
Next.js 14 引入的 Server Actions 是一個讓 App 工程師驚掉下巴的功能。
傳統上,為了送出表單,你需要:
- 開一個
POST /api/submit的 API endpoint。 - 前端
fetch('/api/submit', { method: 'POST' ... })。 - 處理 JSON 序列化與錯誤。
Server Actions 讓你像呼叫本地函式一樣呼叫後端。
實作:按讚功能
// src/features/post/actions.ts
'use server'; // 關鍵字:這段 code 永遠不會打包到客戶端 JS 裡
import { db } from '@/infrastructure/db'; // 假設用 Prisma 連資料庫
import { revalidatePath } from 'next/cache';
export async function toggleLike(postId: string) {
// 這裡直接連 DB,甚至不需要過 HTTP API (如果 DB 允許)
await db.post.update({
where: { id: postId },
data: { likes: { increment: 1 } }
});
// 告訴 Next.js 重新驗證該路徑的快取 (類似 NotificationCenter 更新 UI)
revalidatePath('/posts');
}
在 Component 中使用
// src/components/LikeButton.tsx
'use client';
import { toggleLike } from '@/features/post/actions';
export function LikeButton({ postId }: { postId: string }) {
return (
<button
onClick={async () => {
// 就像呼叫一般函式一樣!Next.js 自動幫你處理 HTTP Post
await toggleLike(postId);
}}
>
👍 Like
</button>
);
}
App 工程師視角:
這根本就是 RPC (Remote Procedure Call)。你不需要寫 Retrofit 介面,不需要定義 Request Body DTO。編譯器會自動幫你確保型別安全。如果 toggleLike 的參數型別改了,前端呼叫的地方立刻紅字報錯。
04. 資料庫操作:Prisma 是你的 CoreData / Room
既然能寫後端,當然也能連資料庫。 前端界最流行的 ORM 是 Prisma。
Schema 定義 (schema.prisma):
model User {
id String @id @default(uuid())
email String @unique
name String?
posts Post[]
}
這就像是定義 CoreData 的 .xcdatamodeld 或 Room 的 @Entity。
查詢資料:
const users = await prisma.user.findMany({
where: { email: { contains: '@gmail.com' } },
include: { posts: true }
});
這語法比 SQL 簡單,也比 CoreData 的 NSFetchRequest 直觀一百倍。
05. 安全性警告:別相信客戶端
雖然寫起來像本地函式,但 Server Actions 本質上還是公開的 API endpoint。
千萬不要這樣寫:
'use server';
export async function deleteUser(userId: string) {
// ❌ 危險!任何人只要送出帶有 userId 的請求就能刪除
await db.user.delete({ where: { id: userId } });
}
必須加上權限檢查:
'use server';
import { getSession } from '@/lib/auth';
export async function deleteUser(userId: string) {
const session = await getSession();
// ✅ 檢查是否登入,且是否有權限
if (!session || session.user.id !== userId) {
throw new Error("Unauthorized");
}
await db.user.delete({ where: { id: userId } });
}
這跟 App 開發一樣,你永遠不能相信 Client 傳來的資料。
06. 小結:全端工程師的誕生
這一篇我們打破了「前端只能切版和打 API」的限制。
- BFF (API Routes):你是資料的轉接頭,負責把後端的爛資料轉成 UI 漂亮的資料。
- Server Actions:你是 RPC 的魔法師,直接從按鈕呼叫資料庫。
- Type Safety:從 DB (Prisma) 到 Server Action 到 UI Component,型別是一條龍打通的。後端改了 DB Schema,前端按鈕立刻報錯。
這是傳統 App 開發 (iOS/Android + Java/Go Backend) 夢寐以求但很難達到的境界。
現在你的 App 功能已經很完整了,但跑起來可能有點「卡卡的」。為什麼頁面切換有時會閃一下?為什麼 Console 出現一堆 Re-render 警告?
在下一篇 【Part 9】,我們將探討 效能優化 (Performance)。我們將使用 React DevTools 和 Next.js 的分析工具,像用 Instruments / Profiler 一樣,抓出效能瓶頸。

關於作者
Ken Huang
熱衷於嘗試各類新技術的軟體開發者,現階段主力為 Android / iOS 雙平台開發,同時持續深耕前端與後端技術,以成為全端工程師與軟體架構師為目標。
最廣為人知的代表作為 BePTT。開發哲學是「以做身體健康為前提」,致力於在工作與生活平衡的基礎上,打造出擁有絕佳使用體驗的軟體服務。
這裡是用於紀錄與分享開發經驗的空間,希望能透過我的實戰筆記,幫助開發者解決疑難雜症並提升效率。
系列文章目錄
- 【App 工程師的前端轉生術 06】樣式系統篇:Tailwind CSS 是你的 Modifier,不是傳統 CSS
- 【App 工程師的前端轉生術 07】全域狀態管理:為什麼 Singleton 在這裡行不通?(Zustand)
- 【App 工程師的前端轉生術 08】前端也能寫後端:BFF 模式與 Server Actions (本文)
- 【App 工程師的前端轉生術 09】效能優化篇:找出那該死的 Re-render
- 【App 工程師的前端轉生術 10】最終章:依賴注入 (DI) 與單元測試策略