Ahmed Abdelaziz

Ecommerce Storefront & Admin ConsoleDesign Docs

Architecture

How the Next.js storefront is organized — App Router, proxy, query layer, and guard system.

Last updated August 2026

Overview

A Next.js 16 storefront and admin console for a custom clothing commerce platform. Customers browse a catalog, pick an exact variant, and check out in three steps; store staff operate a role-gated console for catalog, inventory, orders, coupons, and audit. The browser talks to one origin — the storefront rewrites /api/v1/* to the Express API — so session cookies stay first-party and no token ever touches JavaScript.

The clever part

Reads and writes travel through the same Axios instance. A single next.config.ts rewrite makes browser traffic same-origin, so SameSite=Lax cookies work identically locally (3001 → 3000) and in production (Vercel → Render) with no code change.


Where the code lives

Feature-sliced, not layered by file type. Each business area owns its API, hooks, and UI together:

src/
  app/
    (storefront)/  products/[productId]  cart  checkout  orders  account/*
    (auth)/        login  register       (landing)/  verify-email  forgot-password
    admin/         products  categories  inventory  orders  users  reviews  coupons  analytics  audit  admins
    api/auth-cookie-reset/  dev-ui/
  features/<domain>/  api.ts  hooks.ts  components/  schemas.ts
  lib/api/            axios-instance.ts  client.ts  csrf.ts  queryKeys.ts  session-events.ts
  components/         guards.tsx  layout/  ui/  seo/
  types/              Paginated<T>  catalog.ts  orders.ts  ...
  • Routes are file-system routes grouped by intent: (storefront) customer-facing, (auth) public, admin console. Layouts enforce isolation — app/admin/layout.tsx wraps every admin page in AdminGate.
  • Features are self-contained — features/catalog/api.ts knows its params, features/cart/hooks.ts knows its mutations, features/admin/product-components/* knows its dialogs. No cross-feature imports except through lib/api and types.
  • Types are the contract — hand-written from D:\code\ecommerce's docs/api/**, shared as Paginated<T>, ProductDetail, Cart, Order, etc. One file per domain.

Request flow — one path for everything

Every server-state operation travels the same narrow path:

  • Axios instance (lib/api/axios-instance.ts) is created once: baseURL = NEXT_PUBLIC_API_BASE_URL ?? "/api/v1", withCredentials: true, Content-Type: application/json.
  • Envelope unwrapping (lib/api/client.ts:unwrap) preserves pagination for any response carrying top-level pagination. Listing hooks always type as Paginated<T> — typing as bare T[] compiles but throws at runtime (not iterable), a bug guarded against since 76a6de0.
  • Pagination dialects — the API's meta (page/limit/total/totalPages/hasNext/hasPrev) is normalized from either top-level pagination or nested data.pagination, tolerating both older and current envelope shapes.
  • Query keys (lib/api/queryKeys.ts) are a centralized factory: qk.products(params), qk.cart, qk.admin.orders(params). Full params are part of the key so cache identity is exact.

Authentication and guards

Sessions are opaque cookies, never JWTs, never in JavaScript.

  • Session context (features/auth/session-context.tsx) holds user, isAdmin, isSuperAdmin, and their pending flags. It polls no role field — sessions expose no role — so admin visibility is a probe pattern: GET /admin/products (admin) and GET /admin/audit (super-admin) with 200 vs 403 distinguishing staff tiers. The backend always enforces real authorization; probes drive only UI visibility.
  • Guards (components/guards.tsx) are four gates: AuthGate (requires session or redirects /login?from=), PublicOnlyGate (redirects authed users away from login/register), AdminGate (checks isAdmin, renders ForbiddenCard on failure), SuperAdminGate (checks isSuperAdmin for Analytics).
  • CSRF (lib/api/csrf.ts + interceptor in lib/api/client.ts) is double-submit, deduplicated. fetchCsrfToken() shares one inflight promise; request interceptor attaches x-csrf-token on every write; response interceptor on 403 with a CSRF error code/label retries exactly once after refreshing the token. A concurrent refresh never spawns N parallel fetches.

Admin console — an operations surface, not a demo

The /admin section mirrors every backend admin path with a dedicated editor, each behind AdminGate:

RouteScope
/adminKPI row, revenue trend (recharts), status pipeline links
/admin/analytics, /admin/analytics/coupons, /admin/analytics/expensesP&L ledger, coupon insights — SuperAdminGate
/admin/products + /admin/products/[productId]Product CRUD, variant tabs, image tabs with signed ImageKit uploads
/admin/categoriesCRUD + product-assign dialog, is_active visibility
/admin/inventoryPer-variant ledger, manual reserve/release, reorder checks
/admin/orders, /admin/orders/[orderId]Queue with legal allowedTransitions rendering (order-status-actions.tsx)
/admin/reviews, /admin/users, /admin/coupons, /admin/audit, /admin/adminsModeration, customer management, coupon lifecycle, append-only audit, admin accounts

Order status actions encode the legal transition matrix client-side and render only legal next steps — transitions still carry server-side stock/payment effects, so the UI cannot over-promise.


Media and errors

  • ImageKitnext.config.ts allows ik.imagekit.io / *.imagekit.io; lib/api/imagekit.ts obtains short-lived HMAC params from GET /admin/products/uploads/imagekit-auth (or /uploads/imagekit-auth for reviews). The browser uploads directly; the client then registers the returned HTTPS URL — validated server-side for host, folder allowlist, and extension. The private key never leaves server config.
  • MoneyDecimal(10,2) serialized as fixed strings; rendered via a <Money> helper, never parseFloat.
  • ErrorsnormalizeApiError maps { success: false, message, errors } and legacy { error: { code, message } } into a typed ApiError { status, code, message }. Query retry in app/providers.tsx is disabled for 400/401/403/404/409/422 and retries at most twice otherwise. Session expiry emits session-events.ts and clears the QueryClient.

Honest gaps

  • No client-side integration tests yetvitest is configured for unit tests only; the axios-mock-adapter harness exists but no suite drives it end-to-end beyond client.test.ts envelope cases.
  • Admin probes are UI-only convenience — 200 vs 403 drives visibility; authorization is authoritative on the API.
  • Image remotePatterns are allowlist-only — adding a new CDN requires a next.config.ts edit.