技術架構規劃
- 核心堆疊:Next.js 14+(App Router)、React 18、Tailwind CSS、TypeScript
- 資料層:Supabase 或 Prisma(PostgreSQL);Redis 作快取
- 支付:Stripe(國際)或綠界(在地)
- 部署:Vercel(原生支援 Next.js、整合 CDN);資料庫可用 PlanetScale 或 Supabase
- Starter:可用現成商城樣板快速 fork 再客製(例如 Commerce 類型的 starter 或 headless 方案)
開發流程步驟
| 階段 | 內容與重點 | 關鍵組件/路由 |
|---|---|---|
| 1. 專案初始化 | 建立 Next.js 專案(TypeScript);安裝 Tailwind;加入狀態管理(例如 Zustand);加入圖示套件。 | app/layout.tsx(全域樣式與 Provider) |
| 2. 首頁與導航 | Navbar(搜尋、購物車入口)、Hero、產品網格;用 Server Components/SSR 取得資料;建立動態路由產品頁。 | app/page.tsx、app/products/[id]/page.tsx |
| 3. 產品頁面 | 詳情、變體(尺寸/顏色)、圖片輪播、加入購物車;即時價格更新。 | Server Components fetch 或 SWR(依策略) |
| 4. 購物車與結帳 | 狀態管理購物車;結帳表單與運費;API route 建立 checkout session。 | app/cart/page.tsx、app/api/checkout/route.ts |
| 5. 後台與管理 | Admin dashboard(商品 CRUD)、賣家儀表板;權限用 NextAuth/Clerk(依需求)。 | app/admin/...、Middleware 驗證 |
| 6. 整合支付 | Stripe Checkout 或綠界 SDK;Webhook 處理訂單狀態與通知(Email/訊息)。 | app/api/stripe/route.ts(或 webhook 路由) |
| 7. 測試與部署 | 端對端測試(例如 Cypress);PageSpeed 優化;部署到 Vercel;補上 SEO meta 與 sitemap。 | next.config.js、部署平台設定 |
MVP 商城範例架構
程式碼片段:MVP 專案目錄結構
app/
├── layout.tsx (AuthProvider, CartProvider)
├── page.tsx (首頁: 熱銷、分類)
├── products/
│ └── [slug]/page.tsx (產品詳情)
├── cart/page.tsx
├── checkout/page.tsx
└── api/
├── products/route.ts (CRUD)
└── stripe/route.ts (結帳)
可用 Server Actions 處理表單提交並降低 client bundle;整體以 1–2 週為目標完成可上線的 MVP。
注意事項與擴充(純文字整理)
- 性能:Image 優化、Suspense lazy load、ISR 快取頁面
- 擴充:評價系統、站內搜尋(例如 Algolia)、推薦/個人化、行銷自動化
- 行動優先:以手機結帳流程為優先驗收項目,避免桌機先行造成轉換率損失
部署步驟(通用)
- 建置專案:本地執行 build,確保無錯誤;視需求設定 next.config.js(例如 standalone 輸出)。
- 環境變數:建立 .env.production 或用平台介面注入(DB URL、金流 key 等);避免提交敏感資料。
- 推送到遠端:使用 Git 觸發 CI/CD(每次 push 自動建置與部署)。
- 監控與域名:部署後檢查 logs,綁定自訂域名(CNAME),並做 HTTPS 與快取策略確認。
伺服器選擇比較(台灣考量)
| 平台 | 優點 | 缺點 | 成本(月,小流量) | 步驟重點 |
|---|---|---|---|---|
| Vercel(推薦新手) | 原生 Next.js、自動 SSR/CDN、Edge Functions、設定快速 | 升級後長期費用較固定 | 免費起步,Pro 起 | 連 Git → Import → Deploy;用介面設定環境變數 |
| GCP Cloud Run(台灣最佳延遲) | 無伺服器容器、可選 asia-east1、可自動擴展 | 需要 Docker 與雲端設定能力 | 按請求計費(小流量通常較低) | Dockerfile → 部署到 Cloud Run → 綁定域名與監控 |
| HiNet 本地機房(合規/內網) | 台灣 IDC、低延遲、合規彈性 | 自管成本較高 | 依 VM/服務規格 | VM/容器上線 + Nginx 反代 + 監控/備份 |
台灣優化建議
- 選擇台灣或鄰近 region(如 asia-east1)降低延遲
- 搭配 DNS/CDN(例如 Cloudflare)做快取與安全防護
- 以 PageSpeed 90+ 為目標,並用真實裝置驗證行動結帳流程
藍新金流(NewebPay)整合規劃
藍新金流是台灣常見的支付閘道選擇,適合電商:可涵蓋信用卡、行動支付與超商等多管道,降低台灣消費者付款摩擦。
可提供的支付方式(常見)
- 行動支付:Apple Pay、Google Pay、Samsung Pay、LINE Pay、台灣 Pay、玉山 Wallet(實際開通以商店後台與合約為準)
- 信用卡:VISA、MasterCard、JCB、銀聯;一次付清與分期(分期期數依方案)
- 其他管道:ATM 轉帳、超商代碼/條碼繳費、虛擬帳號;部分情境可搭配物流整合(依需求)
串接方式(服務商角度:以 MPG API 思路整理)
- 註冊與金鑰:於金流商店後台建立商店,取得 MerchantID、HashKey、HashIV。
- 環境變數:在 Next.js 專案以 .env.local 存放金鑰(伺服器端使用,不要暴露給前端)。
- ReturnURL/NotifyURL:
- ReturnURL:付款完成後回到前端顯示結果頁
- NotifyURL:後端接收金流通知,用來更新訂單狀態(以此為準)
- 後端建立交易:在 API Route 產生 TradeInfo(含金額、訂單號、商品描述等),以 AES-256-CBC 加密,再用 SHA256 產生檢核值。
- 前端送出表單:結帳頁以 form POST 把 MerchantID、Version、加密 TradeInfo、檢核值送到金流閘道。
- 回傳與通知驗證:後端接到 Notify 或前端 Return 後,解密資料、驗證檢核碼,再更新訂單狀態(成功/失敗/取消)。
後端 API 範例(純文字示意)
程式碼片段:create-trade API(加密 + 檢核值)
// app/api/create-trade/route.ts(示意)
// 1) 依訂單組 TradeInfo
// 2) AES-256-CBC 加密
// 3) 生成檢核值
// 4) 回傳給前端以 form POST 送到金流
import crypto from 'crypto';
function encryptTradeInfo(tradeInfoJson, hashKey, hashIv) {
const cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(hashKey), Buffer.from(hashIv));
let encrypted = cipher.update(tradeInfoJson, 'utf8', 'hex');
encrypted += cipher.final('hex');
return encrypted;
}
function genCheckValue(encrypted, hashKey, hashIv) {
const raw = `HashIV=${hashIv}&${encrypted}&HashKey=${hashKey}`;
return crypto.createHash('sha256').update(raw).digest('hex').toUpperCase();
}
實務注意事項
- 金鑰只放伺服器端:不要用 NEXT_PUBLIC_。
- 訂單狀態以 NotifyURL 為主:ReturnURL 只做 UX 顯示,避免用戶中途關閉導致狀態不同步。
- 測試與正式要分環境:測試金鑰/測試閘道與正式金鑰/正式閘道要切乾淨。
- 記錄交易 log:便於對帳與排查(時間、訂單號、回傳碼、失敗原因)。
登入與認證(Auth.js / Supabase / Neon)
商城通常需要「會員登入、訂單查詢、地址管理、優惠券、後台權限」等能力。建議把認證當成獨立模組規劃,避免後期補做導致大改。
方案選擇(常見)
- Auth.js:與 Next.js 路由與 middleware 配合度高,適合做多供應商登入或自建帳號系統。
- Supabase Auth:快速提供 Email/Password、OAuth 與 Session,搭配 RLS 做資料存取控制。
- Neon PostgreSQL:以 Postgres 為核心,搭配 Prisma/Drizzle 做資料層;可用 adapter 管理使用者與 session。
Auth.js 基礎架構(純文字示意)
程式碼片段:Auth.js 基礎設定(auth.ts)
// auth.ts(示意)
import NextAuth from "next-auth";
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [],
});
- 路由保護:用 middleware.ts 保護 /dashboard、/admin 等路徑。
- 權限模型:一般會員 vs 管理者;後台路由必須 server-side 驗證。
Supabase Auth 串接重點
- 環境變數:NEXT_PUBLIC_SUPABASE_URL、NEXT_PUBLIC_SUPABASE_ANON_KEY(屬於可公開 key,但仍需控管 RLS)。
- Server-side 取 session:在 Server Component 或 API Route 取用 session,避免只用 client-side 判斷。
- 資料安全:啟用 Row Level Security,讓訂單資料只能被擁有者讀取。
Neon PostgreSQL 整合重點
- DATABASE_URL:伺服器端連線字串(不要用 NEXT_PUBLIC_)。
- Migration:用 Prisma/Drizzle 建表與遷移,讓環境一致。
- Session 資料:透過 adapter 把 session/帳號資料落在資料庫。
實務建議
- 先決定「訪客結帳」是否需要:可大幅影響轉換與資料模型(會員/訂單/地址關係)。
- 把權限與資料存取寫成規則:不要用前端控制顯示來當安全手段。
- 上線前用測試帳號走全流程:註冊/登入/下單/查訂單/退款或取消。
Auth.js 角色權限管理(RBAC)
在 Next.js 商城中使用 Auth.js 做 RBAC,可以清楚區分 user / admin 等角色,保護訂單後台與會員專區。重點是:角色要能被「持久化」、能在「middleware 與 server-side」做驗證,且 UI 顯示只能當輔助,不可當安全機制。
角色資料持久化(JWT + Session)
- 來源:角色可來自 OAuth provider 的 profile,或登入後由資料庫查詢(建議以 DB 為準)。
- JWT callback:把 role 存入 token(例如 token.role)。
- Session callback:把 token.role 暴露到 session.user.role,提供前端顯示與 server-side 判斷。
- TypeScript:需要擴充 Session 的型別,避免 role 報錯。
程式碼片段:JWT / Session callback 持久化 role
// auth.ts(示意)
export const { handlers, auth } = NextAuth({
callbacks: {
async jwt({ token, user }) {
if (user?.role) token.role = user.role;
return token;
},
async session({ session, token }) {
session.user.role = token.role;
return session;
},
},
});
程式碼片段:TypeScript 型別擴充(Session.user.role)
// types/next-auth.d.ts(示意)
declare module "next-auth" {
interface Session {
user: {
role?: string;
};
}
}
中介軟體保護路由(middleware.ts)
建議用 middleware 保護管理後台路由(例如 /admin、/dashboard-admin)。未登入或角色不符則導向未授權頁面。
程式碼片段:middleware 保護 /admin
// middleware.ts(示意)
import { auth } from "./auth";
export default auth((req) => {
const session = req.auth;
const isAdmin = session?.user?.role === "ADMIN";
const isAdminRoute = req.nextUrl.pathname.startsWith("/admin");
if (isAdminRoute && !isAdmin) {
const url = req.nextUrl.clone();
url.pathname = "/unauthorized";
return Response.redirect(url);
}
});
export const config = {
matcher: ["/admin/:path*"]
};
Server Component / Server Action 權限檢查
- Server Component:用 await auth() 取得 session,依 role 決定能否 render 後台內容。
- Server Action:所有寫入行為(退款、出貨、更新訂單)都要在 server 端檢查 role。
程式碼片段:Server Action 內檢查 admin 權限
// actions/updateOrder.ts(示意)
"use server";
import { auth } from "../auth";
export async function updateOrder(...) {
const session = await auth();
if (session?.user?.role !== "ADMIN") {
throw new Error("無權限");
}
// do update
}
Prisma Schema(Role 欄位)
若使用 Prisma + Postgres(Neon 或其他),建議在 User model 加上 role 欄位,並預設為 USER。
程式碼片段:Prisma Role schema
enum Role { USER ADMIN }
model User {
id String @id @default(cuid())
name String?
email String? @unique
emailVerified DateTime?
image String?
role Role @default(USER)
accounts Account[]
sessions Session[]
}
model Account { /* Auth.js 標準 */ }
model Session { /* Auth.js 標準 */ }
遷移與種子(Migration / Seed)
- 開發:使用 migrate dev 產生 migration 並套用。
- 生產:部署時用 migrate deploy 套用 pending migration。
- 種子:建立一個初始 admin 帳號(或把指定 email 升級為 ADMIN)。
程式碼片段:Seed 建立初始 ADMIN
// prisma/seed.ts(示意)
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
async function main() {
await prisma.user.upsert({
where: { email: "admin@mall.com" },
update: { role: "ADMIN" },
create: { email: "admin@mall.com", role: "ADMIN" },
});
}
main().finally(() => prisma.$disconnect());
權限擴充(細粒度 Permissions)
若未來要做更細的權限(例如 order:read、order:refund、product:write),可以把 role 拆成 role-permission 的多對多表,後端用查表結果判斷是否允許動作。
- 方向:User ↔ Role(多對多)、Role ↔ Permission(多對多)
- middleware / server action:從 DB 查出權限集合,再判斷是否具備
Neon vs Supabase(遷移工作流差異摘要)
| 面向 | Neon(偏純 DB) | Supabase(BaaS) |
|---|---|---|
| Prisma Migrate | db push / migrate dev / migrate deploy 都好配合;可用 DIRECT_URL 做 CLI 直連、DATABASE_URL 做 app pooler | 也可用 Prisma;但工作流通常會與 Supabase CLI、RLS、分支策略綁在一起 |
| 分支/測試 | 偏向以 DB branch 測試 schema/資料(視平台策略),適合快速驗證遷移安全 | 偏向 Git + migration/seed 重跑,整合度高但可能較花時間 |
| 定位 | 你要的是 serverless Postgres 本體 + ORM + Auth.js | 你要的是 Auth/Storage/Realtime/RLS 等一整套 BaaS 能力 |
Next.js 環境變數規則
- NEXT_PUBLIC_ 前綴:只用於可在瀏覽器端暴露的變數(例如 Stripe Publishable Key)。
- 無前綴:伺服器端秘密(例如 Stripe Secret Key、DATABASE_URL)。
- 檔案優先:.env.local(本地)通常優先於 .env.production;部署平台的介面設定可覆蓋。
| 名稱 | 是否 NEXT_PUBLIC | 用途示例 |
|---|---|---|
| NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY | 是 | Client 端載入 Stripe.js |
| STRIPE_SECRET_KEY | 否 | Server Actions / API Route 建立付款 |
| DATABASE_URL | 否 | Prisma/Supabase 連線資料庫 |
| NEXTAUTH_SECRET | 否 | Auth 認證用 secret |
程式碼使用範例(純文字)
程式碼片段:Server-side 讀取 Secret Key
// app/api/checkout/route.ts
// Server-side
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
程式碼片段:Client-side 讀取 NEXT_PUBLIC 變數
// Client-side component
// NEXT_PUBLIC_ 變數會在建置時嵌入 bundle
const publishableKey = process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY;
注意事項
- 不要把秘密放進 NEXT_PUBLIC_。
- 部署後若金流爆錯,先檢查環境變數是否真的有注入、build log 是否有讀到正確值。
開始專案
如果你希望用 Next.js 走「先 MVP 上線、再快速迭代」路線,我們可以用既定的路由/資料/金流模組化方式,把時程壓在可控範圍內。