22 KiB
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
- Phase 1 — Discovery & Scaffolding
- Phase 2 — Codebase Triage
- Phase 3 — Documentation
- 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:
-
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 inlib/data/feeding React Server Components. -
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,DynamicLayoutRendererwalks the JSON node tree and maps keys to React components viacomponentMap. 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.jscallscheckEnvVariables()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.ts → listCartOptions() |
| 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:279–318 |
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:24–27 |
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.namVibentecto read fromprocess.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:34–38 - Replace
customer: anyandcart: anywithHttpTypes.StoreCustomer | nullandHttpTypes.StoreCart | null. - Impact: Propagates type safety through the entire renderer pipeline, catching prop mismatches at compile time.
3. Remove dead dependencies
pgand@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:279–318 - 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:
Browse Store → Product Detail → Add to Cart → Checkout → Order ConfirmedLogin → 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.