Shop-Storefront/docs/PRD.md

22 KiB
Raw Blame History

Shop-Storefront — Current State Document

Generated: 2026-03-27 Role: Brownfield analysis by Senior Staff Engineer / Technical Lead Branch at time of analysis: namds/refactor-base-layout


Table of Contents

  1. Phase 1 — Discovery & Scaffolding
  2. Phase 2 — Codebase Triage
  3. Phase 3 — Documentation
  4. Phase 4 — Maintenance Strategy

Phase 1 — Discovery & Scaffolding

Tech Stack

Layer Technology
Framework Next.js 15 (App Router, Turbopack)
Language TypeScript 5.3
UI Runtime React 19 RC (19.0.0-rc-66855b96-20241106)
Styling Tailwind CSS 3 + @medusajs/ui + Radix UI
Commerce Backend Medusa V2 (@medusajs/js-sdk)
Payments Stripe (@stripe/react-stripe-js), PayPal
Package Manager Yarn 3 (Berry)
Database (indirect) pg listed as dependency — Medusa owns the DB connection

Entry Points & Critical Files

File Role
src/middleware.ts True entry point. Intercepts every request, resolves country code from Vercel geo-header or URL, and redirects to /{countryCode}/...
src/app/layout.tsx Root Next.js layout (HTML shell, global styles)
src/app/[countryCode]/(main)/layout.tsx Primary layout shell. Loads the JSON design config, fetches cart + customer, and hands everything to DynamicLayoutRenderer
src/vibentec/configloader.ts Reads active design JSON from config/ at request time
src/vibentec/devJsonFileNames.ts Hardcoded switch that selects which tenant/design JSON is active
src/lib/config.ts Instantiates the Medusa JS SDK singleton
src/lib/data/ All server-side data fetching (cart, customer, products, orders, regions, etc.)

Phase 2 — Codebase Triage

Architectural Pattern

This is a modular monolith storefront using Next.js App Router with a custom JSON-driven dynamic layout layer on top. There are two distinct halves:

  1. Standard Medusa Storefront — The base is a Medusa V2 Next.js starter. It follows a clear modules/templates/components/ hierarchy, with server-side data fetching in lib/data/ feeding React Server Components.

  2. Vibentec UI Builder — A bespoke system in src/vibentec/ that allows the entire page shell (header, nav, footer, banners) to be defined declaratively in JSON config files (/config/). At runtime, DynamicLayoutRenderer walks the JSON node tree and maps keys to React components via componentMap. Multiple tenant/brand configs exist: 3bear, drsquatch, vibentec, mds-starter, medusa-starter, playground.

Core Domain

src/lib/data/ is the data layer. src/modules/ holds all business-domain UI (account, cart, checkout, products, orders, collections, store). The custom differentiator is src/vibentec/ — this is Vibentec's intellectual property layered on top of the OSS Medusa starter.

Technical Debt & Health Assessment

Severity Issue
High REVALIDATE_SECRET=supersecret is committed in .env — must be rotated before production
High A real publishable API key (pk_65b8a...) is hardcoded in the committed .env file
Medium React 19 RC (release candidate) is pinned — not a stable release
Medium Active tenant config is hardcoded in src/vibentec/devJsonFileNames.ts — switching brands requires a code change
Medium LayoutContext uses any for customer and cart — kills type safety at the renderer boundary
Medium pg is listed as a direct dependency but no direct DB usage exists in this storefront
Low Zero test files found anywhere in the project
Low @types/react-instantsearch-dom in devDeps but no instantsearch code exists — unused dependency

Phase 3 — Documentation

1. High-Level System Overview

This is a multi-tenant e-commerce storefront built on Next.js 15 (App Router), serving as the customer-facing frontend for a Medusa V2 headless commerce backend. The application handles the full retail customer journey: browsing products by category or collection, managing a cart, checking out with Stripe or PayPal, and managing account details and order history. All routes are prefixed with a country code (e.g., /us/, /de/), which is resolved at the edge by middleware that consults Medusa's region API and Vercel's geo-IP headers to deliver region-appropriate pricing and shipping rules.

The most significant custom layer is the Vibentec UI Builder (src/vibentec/). Rather than hard-coding the page shell (header, nav, banner bar, footer), the entire layout is declared as a JSON component tree stored in config/*.design.json files. At request time, the primary layout server component reads the active JSON file, passes it to DynamicLayoutRenderer, which walks the tree and resolves component names to actual React components via componentMap. This makes the full layout of the storefront configurable without touching React code — a white-label capability designed to serve multiple brands from a single codebase.

Data flows unidirectionally from the Medusa backend through server-side "use server" functions in src/lib/data/, which use the Medusa JS SDK with Next.js force-cache and tag-based revalidation. There is no client-side global state management (no Redux, Zustand, or React Query). The only React Context used is a minimal ModalContext scoped to the modal component. All authentication state is conveyed via cookies and forwarded as HTTP headers to Medusa.


2. Architecture Diagram

graph TD
    subgraph "Browser"
        Browser["Customer Browser"]
    end

    subgraph "Next.js 15 App (Port 8000)"
        Middleware["src/middleware.ts\n(Edge Runtime)\nGeo-IP → Country Code\nRegion redirect"]

        subgraph "App Router"
            RootLayout["app/layout.tsx\nRoot HTML shell"]
            MainLayout["app/[countryCode]/(main)/layout.tsx\nLoads design JSON\nFetches cart + customer\nDynamicLayoutRenderer"]
            CheckoutLayout["app/[countryCode]/(checkout)/layout.tsx\nCheckout shell"]

            subgraph "Pages"
                Home["/ (main) page.tsx"]
                Store["/store page.tsx"]
                Product["/products/[handle]"]
                Cart["/cart"]
                Checkout["/checkout"]
                Account["/account (Parallel Routes)"]
                Order["/order/[id]"]
                Collections["/collections/[handle]"]
                Categories["/categories/[...category]"]
            end
        end

        subgraph "Vibentec UI Builder (src/vibentec/)"
            ConfigLoader["configloader.ts\nReads config/*.design.json"]
            Renderer["renderer.tsx\nDynamicLayoutRenderer"]
            ComponentMap["component-map.tsx\ncomponentMap registry"]
        end

        subgraph "Modules (src/modules/)"
            Layout["layout/\nHeader, Nav, Footer,\nCart Button, Side Menu,\nMega Menu, Country Select"]
            Products["products/\nGallery, Actions, Price,\nPreview, Tabs, Related"]
            CartMod["cart/\nItems, Summary, Preview"]
            CheckoutMod["checkout/\nAddresses, Payment,\nShipping, Review"]
            AccountMod["account/\nProfile, Orders,\nAddresses, Login"]
            OrderMod["order/\nConfirmation, Details,\nTransfer"]
        end

        subgraph "Data Layer (src/lib/data/)"
            DataFunctions["cart.ts · products.ts\ncustomer.ts · orders.ts\ncategories.ts · collections.ts\nregions.ts · payment.ts\nfulfillment.ts · cookies.ts"]
        end

        subgraph "Config Files (config/)"
            DesignJSON["nam.vibentec.design.json\nnam.3bear.design.json\nnam.drsquatch.design.json\nste.medusa-starter.design.json\n(+ others)"]
        end
    end

    subgraph "External Services"
        MedusaBackend["Medusa V2 Backend\n(localhost:9000)\nProducts, Orders, Carts,\nCustomers, Regions,\nPayment Sessions"]
        Stripe["Stripe\nPayment Processing"]
        PayPal["PayPal\nPayment Processing"]
        S3["AWS S3\nProduct Images"]
    end

    Browser -->|"HTTP Request"| Middleware
    Middleware -->|"Resolves region\nSets _medusa_cache_id cookie"| RootLayout
    RootLayout --> MainLayout
    MainLayout --> ConfigLoader
    ConfigLoader -->|"Reads active .design.json"| DesignJSON
    MainLayout --> Renderer
    Renderer --> ComponentMap
    ComponentMap --> Layout
    MainLayout -->|"props.children"| Pages
    Pages --> Products
    Pages --> CartMod
    Pages --> CheckoutMod
    Pages --> AccountMod
    Pages --> OrderMod
    DataFunctions -->|"Medusa JS SDK\nforce-cache + tag revalidation"| MedusaBackend
    CheckoutMod -->|"Stripe Elements"| Stripe
    CheckoutMod -->|"PayPal SDK"| PayPal
    MedusaBackend -->|"Image URLs"| S3
    Products & CartMod & CheckoutMod & AccountMod & OrderMod --> DataFunctions

3. Local Setup Guide

Prerequisites: Node.js ≥ 20, Yarn 3 (Berry), a running Medusa V2 backend with at least one Region configured.

# 1. Clone and install
git clone <repo-url>
cd Shop-Storefront
yarn install

# 2. Configure environment
cp .env .env.local
# Edit .env.local and set:
#   MEDUSA_BACKEND_URL=http://localhost:9000
#   NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY=<your key from Medusa Admin>
#   NEXT_PUBLIC_BASE_URL=http://localhost:8000
#   NEXT_PUBLIC_DEFAULT_REGION=us
#   REVALIDATE_SECRET=<generate a strong random string — never use "supersecret">

# 3. Select the active brand design (defaults to namVibentec)
# Edit src/vibentec/devJsonFileNames.ts:
#   const fileName = jsonFileNames.namVibentec;  ← change to desired brand key

# 4. Run development server (Turbopack, port 8000)
yarn dev

# 5. Open browser — middleware will redirect to /{DEFAULT_REGION}/
open http://localhost:8000

Build for production:

yarn build
yarn start

next.config.js calls checkEnvVariables() at startup. Missing required env vars will cause a descriptive build failure.


4. Key Domain Entities

Inferred from @medusajs/types (HttpTypes) as used by the data layer, plus local types in src/types/global.ts.

Entity Key Fields Source
StoreProduct id, title, handle, thumbnail, variants[], tags[], metadata, collection, categories[] lib/data/products.ts
StoreProductVariant id, calculated_price, inventory_quantity, options[] Fetched with *variants.calculated_price field selector
StoreCart id, region_id, items[], shipping_address, billing_address, email, promotions[], shipping_methods[], payment_collection lib/data/cart.ts
StoreLineItem id, variant_id, quantity, total, product, variant, thumbnail, metadata Part of StoreCart
StoreRegion id, countries[] (each with iso_2) middleware.ts, lib/data/regions.ts
StoreCustomer id, email, first_name, last_name, addresses[], phone lib/data/customer.ts
StoreOrder id, status, items[], shipping_address, payment_collections[], fulfillments[] lib/data/orders.ts
StoreCartShippingOption id, name, amount, provider_id lib/data/cart.tslistCartOptions()
FeaturedProduct (local) id, title, handle, thumbnail src/types/global.ts
VariantPrice (local) calculated_price, original_price, currency_code, price_type, percentage_diff src/types/global.ts
StoreFreeShippingPrice (local) extends StorePrice + target_reached, target_remaining, remaining_percentage src/types/global.ts
LayoutComponentNode (local) { [ComponentName]: { config?, children? } } src/vibentec/component-map.tsx

Phase 4 — Maintenance Strategy

1. Immediate Risks

Severity Risk Location Action Required
Critical Secrets committed to repository .env Rotate NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY immediately. Regenerate REVALIDATE_SECRET. Use .env.local (already .gitignore'd) for real values.
Critical Silent bug: missing await src/lib/data/cart.ts:339 const cartId = getCartId() is missing await. cartId resolves to a Promise (truthy), so the guard if (!cartId) never fires. setAddresses silently fails to validate cart existence on cold sessions. Fix: const cartId = await getCartId()
High Empty stub functions silently do nothing src/lib/data/cart.ts:279318 applyGiftCard, removeDiscount, removeGiftCard are exported but completely empty. Any UI that calls them will silently succeed with no effect. Either implement for Medusa V2 or remove the exports.
High TypeScript & ESLint errors suppressed at build next.config.js:2427 ignoreBuildErrors: true and ignoreDuringBuilds: true ship broken types silently. Re-enable both before production and resolve resulting errors.
Medium React 19 RC pinned package.json Pre-release runtime in production. Migrate to stable React 19 once available, or document the risk explicitly.

2. Refactoring Opportunities (Low-Hanging Fruit)

Listed in priority order — highest impact for lowest effort first.

1. Externalize active brand config to an environment variable

  • File: src/vibentec/devJsonFileNames.ts
  • Change the hardcoded const fileName = jsonFileNames.namVibentec to read from process.env.VIBENTEC_DESIGN_CONFIG.
  • Impact: Enables true multi-tenant deployment (different brands on different deployments) without any code changes. Currently requires a code commit to switch brands.

2. Type the LayoutContext properly

  • File: src/vibentec/component-map.tsx:3438
  • Replace customer: any and cart: any with HttpTypes.StoreCustomer | null and HttpTypes.StoreCart | null.
  • Impact: Propagates type safety through the entire renderer pipeline, catching prop mismatches at compile time.

3. Remove dead dependencies

  • pg and @types/pg — no direct DB access exists in this storefront.
  • @types/react-instantsearch-dom — no instantsearch code exists.
  • Run: yarn remove pg @types/pg @types/react-instantsearch-dom
  • Impact: Cleaner dependency graph, faster installs, less confusion for new developers.

4. Fix hardcoded data-mode="light"

  • File: src/app/layout.tsx:11
  • If dark mode is planned: drive this from a cookie or prefers-color-scheme. If not: add a comment documenting the intentional lock to light mode.

5. Delete commented-out V1 cart code

  • File: src/lib/data/cart.ts:279318
  • The commented blocks are Medusa V1 patterns. They will never be un-commented as-is. Remove them to reduce noise and cognitive load.

3. Testing Strategy

Given zero existing tests and the RSC-heavy architecture, the recommended approach is outside-in, starting at the data boundaries.

Step 1 — Integration tests for the data layer (src/lib/data/)

These are pure server functions with no React involved — the easiest place to start. Use Vitest with a real (or Docker-composed) Medusa backend in test mode.

Priority order: cart.ts → customer.ts → products.ts → regions.ts

Start with cart.ts — it has the most mutations, the most business logic, and already has a confirmed await bug. Writing a test for setAddresses would have caught that immediately.

Step 2 — Component tests for the Vibentec renderer (src/vibentec/)

The DynamicLayoutRenderer and componentMap are the highest-risk custom code with no safety net. Use React Testing Library with @testing-library/react (RSC-compatible via Next.js jest transform).

Write snapshot tests that pass a known JSON fixture through the full renderer pipeline. This creates a regression guard before any design JSON changes.

// Example fixture test shape
it("renders a Header with a Banner child from JSON", () => {
  const nodes = [
    { Header: { children: [{ Banner: { config: { variant: "nav" } } }] } },
  ]
  const { container } = render(
    <DynamicLayoutRenderer nodes={nodes} context={mockContext} />
  )
  expect(container).toMatchSnapshot()
})

Step 3 — E2E tests for critical user journeys using Playwright

Two journeys that must never break:

  1. Browse Store → Product Detail → Add to Cart → Checkout → Order Confirmed
  2. Login → Account Dashboard → View Order History

Step 4 — CI enforcement

The .github/ directory currently only contains an issue template. Add a GitHub Actions workflow that runs on every PR:

- yarn lint # re-enable ignoreDuringBuilds: false first
- tsc --noEmit # re-enable ignoreBuildErrors: false first
- vitest run # data layer integration tests
- playwright test # critical path E2E

No PR merges to main without all four passing.