Next.js 商城解決方案

使用 Next.js(基於 React)製作商城是高效選擇:SSR/Streaming 有助 SEO 與速度,App Router 可簡化路由與資料載入策略,適合電商的高互動與動態內容。

技術架構規劃

  • 核心堆疊: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)、推薦/個人化、行銷自動化
  • 行動優先:以手機結帳流程為優先驗收項目,避免桌機先行造成轉換率損失

部署步驟(通用)

  1. 建置專案:本地執行 build,確保無錯誤;視需求設定 next.config.js(例如 standalone 輸出)。
  2. 環境變數:建立 .env.production 或用平台介面注入(DB URL、金流 key 等);避免提交敏感資料。
  3. 推送到遠端:使用 Git 觸發 CI/CD(每次 push 自動建置與部署)。
  4. 監控與域名:部署後檢查 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 思路整理)

  1. 註冊與金鑰:於金流商店後台建立商店,取得 MerchantID、HashKey、HashIV。
  2. 環境變數:在 Next.js 專案以 .env.local 存放金鑰(伺服器端使用,不要暴露給前端)。
  3. ReturnURL/NotifyURL:
    • ReturnURL:付款完成後回到前端顯示結果頁
    • NotifyURL:後端接收金流通知,用來更新訂單狀態(以此為準)
  4. 後端建立交易:在 API Route 產生 TradeInfo(含金額、訂單號、商品描述等),以 AES-256-CBC 加密,再用 SHA256 產生檢核值。
  5. 前端送出表單:結帳頁以 form POST 把 MerchantID、Version、加密 TradeInfo、檢核值送到金流閘道。
  6. 回傳與通知驗證:後端接到 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 上線、再快速迭代」路線,我們可以用既定的路由/資料/金流模組化方式,把時程壓在可控範圍內。