Acme Store Docs
Getting Started

Project Structure

An annotated tour of the repository layout and where each concern lives.

This page is a map. It walks the repository top-down, then dives into src/, so you know exactly where to reach when you want to change branding, add an API route, swap the commerce backend, or add a language. Every path below is real — open them alongside this page.

Top level

Repository root
ecommerce-boilerplate/
├─ content/docs/          # This documentation (Fumadocs MDX source)
├─ docs/                  # Backend adapter guides + CI recipe (plain markdown)
│  ├─ adapters/
│  │  ├─ postgres-prisma.md
│  │  └─ supabase.md
│  └─ ci.example.yml
├─ src/                   # All application code (see below)
├─ public/                # Static assets (category SVGs, favicon, images)
├─ prisma/
│  └─ schema.prisma       # Postgres/Prisma backend schema (optional)
├─ supabase/
│  └─ schema.sql          # Supabase (Postgres) backend schema (optional)
├─ scripts/
│  └─ seed.mjs            # Idempotent demo-data seeding
├─ tests/                 # Vitest unit suite (see "tests/" below)
├─ firestore.rules        # Firestore security rules (admin/role enforcement)
├─ storage.rules          # Firebase Storage security rules
├─ next.config.mjs        # Next.js config + Fumadocs MDX plugin
├─ tailwind.config.ts     # Tailwind theme (reads the --brand* design tokens)
├─ source.config.ts       # Fumadocs content-source config
├─ prisma.config.ts       # Prisma CLI config
├─ postcss.config.mjs
├─ components.json         # shadcn/ui generator config
├─ vitest.config.ts
├─ tsconfig.json
├─ README.md
├─ ROADMAP.md
└─ SECURITY.md

The store is quote-based by default: browse → cart → submit a quote request, with no payment step. prisma/, supabase/, and the Stripe/Resend wiring are all optional and environment-gated — a default build runs on Firebase alone and never needs them. See environment variables.

The src/ tree

src/
src/
├─ app/                   # Next.js App Router
│  ├─ [locale]/           # Locale-prefixed storefront + admin (de-facto root layout)
│  ├─ api/                # Route handlers (server endpoints)
│  ├─ docs/               # Renders /docs from content/docs (NOT locale-prefixed)
│  ├─ globals.css         # Design tokens + Tailwind layers
│  ├─ robots.ts           # /robots.txt
│  ├─ sitemap.ts          # /sitemap.xml
│  └─ favicon.ico
├─ components/            # React components, grouped by surface (see below)
├─ config/
│  └─ site.ts             # Single-file branding: name, contact, nav, currencies…
├─ contexts/             # Client React context providers (cart, user)
├─ hooks/                # Reusable client hooks
├─ lib/                   # Framework-agnostic logic (see "src/lib" below)
├─ types/                # Shared domain types (product, quote, customer…)
└─ middleware.ts          # Locale-prefix redirect (edge runtime)

src/app/[locale] — the routed app and de-facto root layout

Every storefront and admin page lives under a [locale] segment, so URLs are locale-prefixed (/en/products, /es/cart). There is no src/app/layout.tsx — so src/app/[locale]/layout.tsx is the root layout. It owns <html lang> and <body>, mounts every top-level provider, and renders the shared Header, Footer, and Toaster:

src/app/[locale]/layout.tsx
return (
  <html lang={locale}>
    <body className={`${display.variable} ${GeistSans.variable} ${GeistMono.variable} font-sans`}>
      <ThemeStyle />
      <OrganizationJsonLd />
      <UserProvider>
        <LocaleProvider initialLocale={locale}>
          <CurrencyProvider initialCurrency={currency}>
            <CartProvider>
              <Header />
              {children}
              <Toaster />
              <Footer locale={locale} />
            </CartProvider>
          </CurrencyProvider>
        </LocaleProvider>
      </UserProvider>
    </body>
  </html>
);

generateStaticParams() pre-renders one route tree per configured locale, and an unknown prefix (/fr/...) calls notFound() instead of silently falling back.

The page segments under [locale]:

SegmentRoutePurpose
page.tsx/{locale}Landing page (hero, categories, featured, contact)
products//{locale}/productsCatalog + product detail pages
cart//{locale}/cartCart review
checkout//{locale}/checkoutQuote-request (and optional Stripe) checkout
login//{locale}/loginGoogle + email-link sign-in
complete-login//{locale}/complete-loginPasswordless email-link landing
account//{locale}/accountProfile, addresses, quote/order history
admin//{locale}/adminAdmin dashboard (role-gated)
about//{locale}/aboutAbout page
privacy-policy/, terms-of-service/, cookie-policy//{locale}/…Legal pages

src/app/api — route handlers

Server endpoints, one folder per resource. See the API reference for full request/response detail.

src/app/api/
api/
├─ store/                          # Public read-only catalog API
│  ├─ route.ts                     # GET /api/store
│  ├─ categories/route.ts          # GET /api/store/categories
│  └─ products/
│     ├─ route.ts                  # GET /api/store/products
│     └─ [id]/route.ts             # GET /api/store/products/:id
├─ commerce/[service]/[method]/route.ts   # POST RPC layer for SQL backends
├─ checkout/
│  ├─ route.ts                     # POST /api/checkout (Stripe, optional)
│  └─ config/route.ts              # GET /api/checkout/config
├─ webhooks/stripe/route.ts        # Stripe webhook (optional)
├─ quotes/notify/route.ts          # Quote email notifications (optional)
└─ notifications/status/route.ts   # Email-integration status probe

The /api/store/* catalog endpoints are always available (rate-limited, cache headers). The commerce/[service]/[method] route is the RPC layer used only when a SQL backend is selected — see commerce backends. The checkout, webhooks, and quotes routes are env-gated.

src/app/docs — this documentation

src/app/docs/[[...slug]]/page.tsx and src/app/docs/layout.tsx render the Fumadocs site from the MDX in content/docs/. This subtree is deliberately not locale-prefixed — the middleware excludes /docs, /api, and static files from the locale redirect. The MDX is compiled at build time by the Fumadocs plugin wired into next.config.mjs:

next.config.mjs
const withMDX = createMDX();
export default withMDX(nextConfig);

src/components

Presentational and interactive components, grouped by the surface they serve:

FolderWhat lives here
home/Landing sections plus the shared Header and Footer
product/Product cards, galleries, detail views
cart/Cart drawer, line items
admin/Admin dashboard UI
login/, userProfile/Auth and account UI
header/, preferences/Header controls and preference menus
i18n/, currency/, theme/Locale / currency / theme switchers (ThemeStyle injects --brand*)
emailing/, legal/, seo/Newsletter/contact, legal copy, JSON-LD (OrganizationJsonLd)
ui/30+ shadcn/ui (Radix) primitives — buttons, dialogs, toaster, etc.

src/lib

Framework-agnostic logic — the parts you can unit-test without a browser. The subdirectories are the important seams:

src/lib/
lib/
├─ commerce/       # Backend-agnostic commerce service layer (see below)
├─ firebase/       # Firebase SDK wiring: auth, Firestore, Storage, admin
├─ i18n/           # Dictionaries + locale resolution
├─ currency/       # Currency provider + server-side active-currency resolution
├─ theme/          # Brand presets + server-side active-theme resolution
├─ api/            # store.ts — helpers behind the public /api/store endpoints
├─ money.ts        # formatMoney — the single price-formatting seam
├─ cart.ts, order-total.ts, checkout-line.ts, checkout-metadata.ts
├─ inventory.ts, discounts.ts            # Stock + discount logic
├─ email.ts, stripe.ts                   # Optional Resend / Stripe helpers
├─ audit.ts, rate-limit.ts               # Cross-cutting concerns
├─ motion.ts, utils.ts                   # Animation + misc helpers
└─ source.ts                             # Fumadocs content source loader

src/lib/commerce — the backend-agnostic service layer

The single seam between the app and its commerce backend. Every adapter satisfies the same CommerceServices interface and emits the same lifecycle events, so swapping backends is a data/config change, not an app rewrite.

FileRole
types.tsBackend-neutral interfaces: ProductService, CartService, QuoteService, OrderService, DiscountService, CustomerService, AdminService (grouped as CommerceServices)
firebase.tsFirebase adapter — delegates to FirebaseDBService, runs client-side
http.tsClient HTTP adapter — forwards each call to the /api/commerce RPC route (used with SQL backends)
server/index.tsgetServerCommerce() — resolves the SQL adapter from COMMERCE_BACKEND
server/supabase.tsSupabase (Postgres) adapter — server-side, service-role key
server/prisma.tsPostgres/Prisma adapter — server-side, direct DATABASE_URL
events.tsTyped, dependency-free event bus (on, once, emit)
index.tsBinds commerce to the active adapter and re-exports everything

Read the full contract in commerce backends overview and the commerce RPC and events pages.

src/lib/firebase

Firebase SDK wiring. config.ts reads the NEXT_PUBLIC_FIREBASE_* env vars; authService.ts handles Google + passwordless email-link sign-in; dbService.ts (FirebaseDBService) is the Firestore data layer; storageService.ts handles uploads; admin.ts is the server-side firebase-admin instance used to verify ID tokens in the RPC route; firebaseDataConverters.ts and firebaseUtils.ts hold typed converters and helpers.

src/lib/i18n

Locale dictionaries and resolution. en.ts and es.ts are the shipped keyed dictionaries; index.ts registers them and exposes resolveLocale; server.ts resolves server-side; LocaleProvider.ts is the client provider; navigation.ts builds locale-aware links. Adding a language is a data change: drop src/lib/i18n/<code>.ts and register it. See internationalization.

src/lib/currency

CurrencyProvider.tsx is the client provider; server.ts exposes getActiveCurrency() (read in the root layout) so the first server paint already reflects the shopper's cookie-selected currency. Prices are stored in the base currency and converted for display by formatMoney in src/lib/money.ts. See currency.

src/lib/theme

presets.ts holds the brand color presets; server.ts resolves the active preset server-side. The <ThemeStyle /> component (src/components/theme) injects the active preset's --brand* CSS variables. See branding & theming.

src/config/site.ts — single-file branding

The single place to rebrand the boilerplate: store name, tagline, description, url, contact, social links, mainNav / legalNav, currency and currencies, defaultLocale / locales, the theme presets, and the exported productCategories array. Components read from this config instead of hardcoding copy.

src/config/site.ts
export const siteConfig = {
  name: "Acme Store",
  tagline: "Everything you need to launch your online store",
  currency: { code: "USD", locale: "en-US" },
  defaultLocale: "en",
  locales: [
    { code: "en", label: "English" },
    { code: "es", label: "Español" },
  ],
  // …contact, links, mainNav, theme presets, etc.
} as const;

Full field-by-field reference: site configuration.

src/contexts and src/hooks

src/contexts holds the two client providers mounted in the root layout: cartContext.tsx (CartProvider) and userContext.tsx (UserProvider). src/hooks holds reusable client hooks such as useIsAdmin.ts.

src/types

Shared domain types consumed across the app: product.ts, quote.ts, customer.ts, and personalInfo.ts.

Optional backend directories

These exist only for the optional SQL backends and are ignored by a default Firebase build (their packages are kept external in next.config.mjs):

PathUsed by
prisma/schema.prismaPostgres/Prisma adapter (COMMERCE_BACKEND=postgres)
supabase/schema.sqlSupabase adapter (COMMERCE_BACKEND=supabase)
docs/adapters/postgres-prisma.md, docs/adapters/supabase.mdHuman setup guides for each

Security rules

Two rule files ship at the repository root and enforce access on the default Firebase backend:

  • firestore.rules — Firestore access control, including the admins role collection that gates the admin dashboard.
  • storage.rules — Firebase Storage access control for product image uploads.

middleware.ts

src/middleware.ts makes the locale prefix mandatory. Unprefixed requests (/, /products, …) are 307-redirected to the visitor's active locale, resolved in this order: the locale cookie → the best match from the Accept-Language header → siteConfig.defaultLocale. It runs on the edge and is kept dependency-free.

src/middleware.ts
export const config = {
  matcher: [
    "/((?!api|docs|_next|_vercel|.*\\..*|sitemap\\.xml|robots\\.txt|favicon\\.ico).*)",
  ],
};

The matcher excludes api, the docs subtree, Next/Vercel internals, any path with a file extension, and the SEO/meta files — which is why /docs and /api/* are never locale-prefixed.

tests/

A flat Vitest unit suite covering the framework-agnostic src/lib logic and the design system — for example money.test.ts, discounts.test.ts, inventory.test.ts, order-total.test.ts, i18n.test.ts, theme.test.ts, commerce-surface.test.ts, and button-variants.test.ts. Run them with npm test.

Where do I go to…?

On this page