Admin 控制台核心(Next.js 電商後台)

Next.js admin 控制台適合用來管理電商的訂單、用戶、積分與營運設定。建議使用 App Router 與 Server Actions 建置:安全、效能佳、可避免管理操作在前端洩漏敏感邏輯,並可搭配 RBAC 做精準權限控管。

控制台架構

建議採用「側邊導航 + 頂部工具列 + 主內容區」的標準後台資訊架構,並把核心模組做成可重用的列表與詳情頁。

  • 側邊導航:Dashboard、用戶、訂單、積分、設定(支援展開/收合)
  • 頂部 Header:搜尋、通知、快速操作(例如匯出、建立優惠)
  • 主內容:表格(DataTable)與圖表(銷售趨勢、今日訂單、總營收)

UI 設計原則(響應式 + 可操作性)

  • 佈局:固定側邊欄,內容區使用 responsive grid;支援暗黑模式
  • 元件:DataTable(排序/篩選/分頁)、Chart(趨勢)、Toast(回饋)
  • 電商專屬:訂單狀態 Badge、用戶角色下拉、積分調整表單、退款/出貨操作
  • UI 庫方向:Tailwind + shadcn/ui + icon 套件(加速 DataTable、Dialog、Dropdown)

權限與安全(RBAC + Middleware + Server Actions)

後台安全的核心是「所有資料讀寫都在 server-side 驗證」。前端只負責呈現與送出操作,不應持有敏感邏輯。

  • 路由層:middleware 保護 /admin/*,未授權導向 /unauthorized 或後台登入頁
  • 資料層:API / Server Actions 內再次驗證角色(避免繞過)
  • 角色:至少區分 USER / ADMIN;可擴充 SUPERADMIN、readonly 等
程式碼片段:middleware 保護 /admin
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(req: NextRequest) {
  const token = req.cookies.get('admin-token');
  if (req.nextUrl.pathname.startsWith('/admin') && !token) {
    return NextResponse.redirect(new URL('/admin/login', req.url));
  }
  return NextResponse.next();
}

export const config = { matcher: '/admin/:path*' };
程式碼片段:Server Action 內驗證 admin
'use server';
import { revalidatePath } from 'next/cache';

export async function updateOrderStatus(formData: FormData) {
  // const session = await auth();
  // if (session?.user?.role !== 'ADMIN') throw new Error('無權限');

  const id = formData.get('id');
  const status = formData.get('status');

  // await prisma.order.update({ where: { id }, data: { status } });
  revalidatePath('/admin/orders');
}

API 端點設計(App Router Route Handlers)

可用 Route Handlers 做 RESTful API(適合第三方整合與工具調用),同時把管理操作盡量用 Server Actions 直接綁表單,減少額外 API 往返。

程式碼片段:端點目錄(示意)
app/api/admin/orders/route.ts          // GET /api/admin/orders?page=1&status=pending
app/api/admin/users/[id]/route.ts     // GET/PUT/DELETE /api/admin/users/123
app/api/admin/points/route.ts         // POST /api/admin/points { userId: '123', points: 100 }

實作範例:用戶管理頁

用戶管理建議以 DataTable 為核心:欄位包含名稱、Email、角色、活躍狀態、動作(編輯/刪除)。更新角色建議走 Server Action,並在 server-side 驗證 admin。

程式碼片段:app/admin/users/page.tsx(示意)
import { DataTable } from '@/components/admin/data-table';
import { columns } from './columns';

export default async function UsersPage({ searchParams }: { searchParams: { page?: string } }) {
  // const session = await auth();
  // if (session?.user?.role !== 'ADMIN') redirect('/unauthorized');

  const users = await db.user.findMany({ /* 查詢 */ });
  return <DataTable columns={columns} data={users} />;
}

訂單管理:列表 / 篩選 / 狀態更新 / 通知

訂單管理聚焦在:列表檢視、狀態更新、細節編輯與通知整合。建議支援分頁、狀態篩選、日期範圍、客戶搜尋,以及匯出 CSV/Excel。

API(列表與更新)示意

程式碼片段:GET /api/admin/orders(分頁 + 篩選)
export async function GET(req: NextRequest) {
  const { searchParams } = new URL(req.url);
  const page = parseInt(searchParams.get('page') || '1');
  const status = searchParams.get('status');
  const limit = 20;

  const orders = await prisma.order.findMany({
    where: status ? { status } : {},
    skip: (page - 1) * limit,
    take: limit,
    orderBy: { createdAt: 'desc' },
  });

  return Response.json({ orders });
}
程式碼片段:PATCH /api/admin/orders/[id](更新狀態 + 觸發通知)
export async function PATCH(req: NextRequest, { params }) {
  const data = await req.json(); // { status: 'shipped', tracking: 'ABC123' }

  const order = await prisma.order.update({
    where: { id: params.id },
    data,
    include: { user: true },
  });

  // await sendOrderUpdateEmail(order);
  return Response.json(order);
}

Server Actions(推薦:避免多餘 API)

程式碼片段:updateOrderStatus(revalidatePath)
'use server';
import { revalidatePath } from 'next/cache';

export async function updateOrderStatus(id: string, newStatus: string) {
  // await prisma.order.update({ where: { id }, data: { status: newStatus } });
  // await sendStatusEmail(order, newStatus);
  revalidatePath('/admin/orders');
}
程式碼片段:app/admin/orders/page.tsx(管理頁骨架)
import { DataTable } from '@/components/ui/data-table';
import { columns } from './columns';
import { getOrders } from './actions';

export default async function OrdersPage({ searchParams }) {
  const orders = await getOrders(searchParams);
  return (
    <div className="space-y-4">
      <div className="flex justify-between">
        <h1>訂單管理</h1>
        <Button>匯出CSV</Button>
      </div>
      <DataTable columns={columns} data={orders} />
    </div>
  );
}
操作 API 方法 功能
列表查詢 GET /api/admin/orders 分頁 / 篩選
更新狀態 PATCH /api/admin/orders/[id] 出貨 / 取消 + 通知
刪除 DELETE /api/admin/orders/[id] 軟刪除
匯入/匯出 POST /api/admin/orders/bulk CSV 批量

檔案結構(建議)

程式碼片段:Admin + API 檔案結構
app/
├── api/
│   └── admin/
│       └── orders/
│           ├── route.ts          // GET 列表
│           ├── [id]/
│           │   ├── route.ts      // GET/PATCH/DELETE 單筆
│           │   └── page.tsx      // 細節頁(選用)
│           └── actions.ts        // Server Actions
├── admin/
│   └── orders/
│       ├── page.tsx             // 管理頁
│       └── columns.ts           // DataTable 欄位
└── middleware.ts                // 權限檢查

標準訂單狀態流程

後台更新狀態時,建議只在狀態「真的變更」時才觸發通知;並把每次變更記錄成稽核 log(adminId、時間、舊狀態、新狀態、備註)。

  • Pending:新訂單,待付款確認
  • Processing:已收款 / 揀貨中
  • Shipped:已出貨(可附追蹤碼)
  • Delivered:已送達
  • Complete:歸檔
  • Cancelled / Refunded:取消 / 退款
程式碼片段:PATCH 更新狀態時比對 old/new(示意)
export async function PATCH(req: NextRequest, { params }) {
  const data = await req.json();
  const order = await prisma.order.update({ where: { id: params.id }, data });

  // if (data.status !== order.status) {
  //   await sendStatusEmail(order, data.status);
  // }

  // revalidatePath('/admin/orders');
  return NextResponse.json(order);
}

使用與最佳實務

  • 權限:API 內做 authAdmin 檢查,前端顯示只是輔助
  • 快取:列表查詢可用 revalidatePath 或設定 revalidate 秒數
  • 輸入驗證:用 schema 驗證(例如 Zod),避免髒資料
  • 錯誤監控:把後台的關鍵錯誤上報(例如 Sentry),並保留操作 log
  • 效能:分頁、索引(orderId、status、createdAt)、避免一次查出大量關聯資料

開始專案

如果你希望把電商後台(訂單/用戶/積分)做成可擴充的控制台,我們可以先以 MVP 後台(訂單列表 + 狀態更新 + RBAC)上線,再逐步補上圖表、批次匯入匯出與更細的權限。