You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A modern, production-ready eCommerce storefront built with Next.js 16 (App Router), TypeScript, Tailwind CSS v4, and Zustand. Consumes the Laravel REST API backend via a secure internal proxy architecture.
This frontend application is a headless eCommerce storefront that powers the customer-facing shopping experience. It communicates with a Laravel 12 backend API through a secure proxy layer, ensuring internal API secrets never reach the browser.
At a Glance
Metric
Count
Total Source Files (TS/TSX)
118
Pages (App Router)
36
Components
29
Services
17
Zustand Stores
3
TypeScript Type Modules
9
Utility Modules
5
Next.js API Routes
3 proxy routes
Public Assets
2
Key Highlights
🏗️ Next.js 16 App Router — Full server/client component architecture with React 19
🔒 Secure Proxy Architecture — Three-layer API proxy (internal, public, direct) ensures secrets stay server-side, with path-traversal guards on every catch-all route
🛡️ Server-Side HTML/CSS Sanitization — All dangerouslySetInnerHTML usage (CMS pages, product/landing page rich content) is passed through sanitizeHtml()/sanitizeCss() before rendering to prevent stored XSS from admin-authored content
🛒 Guest + Authenticated Checkout — Full checkout flow supporting both guest and registered users
📊 Multi-Platform Analytics — GTM, GA4, Facebook Pixel, and TikTok Pixel with standardized eCommerce events
🗺️ Bangladesh Location Picker — Division → District → Upazila → Union cascading selects in checkout
💬 Live Chat Widget — WhatsApp + Messenger floating chat with configurable position/color
🔍 SEO Optimized — Dynamic robots.ts, sitemap.ts, site verification meta tags, and per-page metadata
🎨 Teal Accent Design System — Clean slate/gray palette with teal accent colors, CSS custom properties, and Tailwind v4
⚡ Smart Caching — Internal GET requests use in-memory + localStorage cache with retry logic and deduplication
🚀 Turbopack Support — npm run dev:turbo for faster development builds
💳 Stripe + bKash Payments — Full payment flows with dedicated payment pages
📦 Side Cart Drawer — Slide-out cart with free shipping progress indicator
🏷️ Landing Pages — Dynamic marketing landing pages at /l/{slug}
📋 Dynamic Checkout Form — Admin-configurable checkout fields rendered from backend settings schema
Tech Stack
Layer
Technology
Version
Framework
Next.js (App Router)
16.2.4
Language
TypeScript
^6.0.3
React
React + React DOM
^19.2.5
Styling
Tailwind CSS
^4.2.2
State Management
Zustand
^5.0.12
HTTP Client
Axios
^1.15.0
Icons
Lucide React
^1.14.0
Toast Notifications
React Hot Toast
^2.6.0
Payment (Stripe)
@stripe/react-stripe-js + @stripe/stripe-js
^6.2.0 / ^9.2.0
CSS Utilities
clsx
^2.1.1
Typography Plugin
@tailwindcss/typography
^0.5.19
Validation (Dev)
Zod
^4.3.6
Linting
ESLint + eslint-config-next
^9.39.4 / 16.2.4
Build Tool
esbuild
^0.28.0
PostCSS
postcss + @tailwindcss/postcss + autoprefixer
^8.5.10 / ^4.2.2 / ^10.5.0
Auth
Bearer Token (Sanctum)
—
Deployment
Node.js (PM2 / aaPanel)
20.9+
Architecture
API Proxy Architecture
The frontend uses a three-layer proxy system to communicate with the backend API, ensuring security and flexibility:
Browser (Client)
│
├─ /api/internal/[...path] ──→ Backend /api/v1/{path} + X-Internal-Secret header
│ (Storefront data: products, categories, settings, flash sales)
│ Server-side only — secret never exposed to browser
│
├─ /api/proxy/[...path] ──→ Backend /api/v1/{path} + Bearer token from request
│ (Authenticated actions: cart, orders, payments, wishlist)
│ Token forwarded from client → proxy → backend
│
└─ /api/public/[...path] ──→ Backend /api/v1/{path}
(Public auth: login, register, checkout, tracking)
Direct passthrough with optional Bearer token
Special: /api/public/orders ──→ Backend /api/v1/orders
(Order creation with X-Internal-Secret — for guest checkout)
Why Three Layers?
Proxy Route
Purpose
Security Model
/api/internal/[...path]
Read-only storefront data (products, categories, settings)
Server injects X-Internal-Secret — client never sees it
/api/proxy/[...path]
Authenticated user actions (cart, profile, orders)
Client's Bearer token forwarded through proxy
/api/public/[...path]
Public endpoints (auth, checkout, tracking)
No secret needed; optional Bearer token passthrough
/api/public/orders
Order placement (special handler)
Server injects X-Internal-Secret for guest order support
Hardening Notes (Applied)
Path-traversal guards: every catch-all proxy route (internal, proxy, public) rejects any path segment that decodes to . or .. before it's joined into the upstream URL, closing off attempts to escape the allowed endpoint prefixes.
Allowlisted paths only: /api/internal/* only forwards to an explicit prefix allowlist (categories, products, settings, flash-sales, etc.) — an unlisted path returns 404 rather than being forwarded blind.
Secrets never reach the client bundle: INTERNAL_API_SECRET is read from process.env inside a Route Handler (server-only code), never from a NEXT_PUBLIC_* variable, so it cannot leak into client JS.
Stored-XSS defense-in-depth: all dangerouslySetInnerHTML call sites run content through sanitizeHtml()/sanitizeCss() first.
Data Flow
User Action → Component → Zustand Store → Service → Axios Instance → Next.js API Route → Laravel Backend
│
┌───────┴───────┐
│ internalApi │ (for /api/internal/* — storefront reads)
│ api (default)│ (for /api/proxy/* — auth user actions)
└───────────────┘
Auth Flow
User submits credentials to /api/public/auth/login
Backend returns Bearer token
Token stored in localStorage via setAuthToken()
Zustand auth.store persists user state
All subsequent api (proxy) requests auto-attach Authorization: Bearer <token> via interceptor
On 401 response: token cleared, user redirected to /login
Optional CSRF flow supported (disabled by default for token-based auth)
ShippingMethod, ShippingRate, Division, District, Upazila, Union
Shipping and BD location types
Utilities
helpers.ts — Core Helper Functions
Function
Description
cn(...inputs)
Tailwind class name merger using clsx
formatPrice(price)
Format price with BDT taka sign (৳), smart decimal handling
getImageUrl(url)
Auto-rewrite image extensions to .webp; resolve relative URLs against API origin
truncateText(text, maxLength)
Truncate text with ellipsis
product-grid.ts — Product Grid Configuration
Export
Description
normalizeDesktopColumns(value)
Normalize desktop grid columns (3, 4, 5, or 6)
normalizeMobileColumns(value)
Normalize mobile grid columns (1 or 2)
getProductGridClassName(desktop, mobile)
Generate Tailwind grid classes with spacing options
DEFAULT_PRODUCT_GRID_COLUMNS_DESKTOP
Default: 5 columns
DEFAULT_PRODUCT_GRID_COLUMNS_MOBILE
Default: 2 columns
sanitize.ts — HTML/CSS Sanitization
Function
Description
sanitizeHtml(html)
Strips <script> tags, inline event handler attributes (onclick, onerror, etc.), and javascript:/data:text/html URIs from a raw HTML string before it's passed to dangerouslySetInnerHTML. Used everywhere the backend can return admin-authored rich content: CMS pages, product/landing page descriptions, static legal pages.
sanitizeCss(css)
Strips expression(), javascript: URIs, and @import from raw CSS strings before injection into <style> blocks.
Applying this at render time is defense-in-depth — it assumes the backend response could theoretically be compromised or an admin account misused, and refuses to trust HTML/CSS coming from the API as safe to inject verbatim.
tracking.ts — Multi-Platform Analytics (10KB)
Standardized eCommerce event tracking across four platforms:
Uses Next.js Turbopack for significantly faster HMR and rebuilds.
Linting
npm run lint
Clean Build Artifacts
npm run clean
Removes .next, out, and tsconfig.tsbuildinfo.
Build & Production
Build
npm run build
Start Production Server
npm run start
The start script auto-reads the PORT from .env if set, defaulting to 3000.
Production Deployment
Option 1: PM2 (Recommended)
# Install PM2 globally
npm install -g pm2
# Start with PM2
pm2 start npm --name "ecommerce-frontend" -- start
# Save and configure auto-restart
pm2 save
pm2 startup
Option 2: aaPanel
Go to Website → Node project → Add Node project
Set the run command to npm start
Map your domain — aaPanel auto-configures Nginx reverse proxy to port 3000
Option 3: Direct Node.js
# Build first
npm run build
# Start production server
npm run start
Production Checklist
Set NEXT_PUBLIC_API_URL to production API URL
Set INTERNAL_API_SECRET matching backend value
Set NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY to live Stripe key
Set NEXT_PUBLIC_SITE_URL for SEO (robots.txt, sitemap.xml)
Configure reverse proxy (Nginx) for port forwarding
Enable HTTPS on the reverse proxy
Set up process manager (PM2) for auto-restart
Verify all tracking integrations are configured in admin panel
Troubleshooting
Symptom
Cause
Fix
Only the header/footer render — products, categories, settings, everything data-driven is blank
No .env.local file, or INTERNAL_API_SECRET isn't set. /api/internal/[...path] returns HTTP 500 ("Internal API secret is not configured.") for every request when the secret is missing, and nearly every storefront section reads through that proxy. The header still renders because it doesn't depend on it.
Create .env.local from .env.example and set NEXT_PUBLIC_API_URL + INTERNAL_API_SECRET. The secret must match the backend's INTERNAL_API_SECRET exactly.
Products/categories load in curl against the backend directly but not through the frontend
The frontend's INTERNAL_API_SECRET doesn't match the backend's, or NEXT_PUBLIC_API_URL points at the wrong host/port.
Compare both .env/.env.local files side by side; restart npm run dev after changing env vars (Next.js only reads them at server start).
Cannot find module 'playwright' (or similar) when running a one-off Node script against this app
Node's CJS resolution walks up from the script's own directory, not the current working directory — a script outside frontend/ won't see frontend/node_modules.
Run npm install --no-save <package> inside frontend/ and place the script directly inside the project directory before running it.
Build succeeds but ESLint reports dozens of pre-existing warnings/errors unrelated to your change
This is known lint debt, not a regression — verified by stashing changes and re-linting the base branch (same error count with/without a given change set).
Only worry about lint errors in files your diff touches; don't try to fix unrelated pre-existing violations in the same PR.
API Integration
This frontend is a headless client for the Laravel backend API. For the complete list of all API endpoints, request/response contracts, and authentication requirements, refer to the backend's API_DOCUMENTATION.md.