Back to Spree

Architecture

docs/developer/storefront/nextjs/architecture.mdx

5.6.07.9 KB
Original Source

Server-First Pattern

The storefront follows a server-first architecture where all API calls are made server-side. The Spree API key is never exposed to the browser.

Browser → Server Action → @spree/sdk → Spree API
         (with httpOnly cookies via src/lib/spree helpers)
  • Server Actions (src/lib/data/) — call @spree/sdk directly with auth/cookie helpers from src/lib/spree
  • httpOnly Cookies — auth tokens, cart tokens, and locale are stored securely
  • No Client-Side API Calls — the Spree API key stays on the server
  • Auto-Localization — locale and country are read from cookies via getLocaleOptions()

Project Structure

src/
├── app/
│   └── [country]/[locale]/          # Localized routes
│       ├── (storefront)/            # Main storefront layout
│       │   ├── page.tsx             # Homepage
│       │   ├── account/             # Customer account
│       │   │   ├── addresses/       # Address management
│       │   │   ├── credit-cards/    # Saved payment methods
│       │   │   ├── gift-cards/      # Gift cards
│       │   │   ├── orders/          # Order history
│       │   │   │   └── [id]/        # Order details
│       │   │   ├── profile/         # Profile settings
│       │   │   └── register/        # Registration
│       │   ├── cart/                # Shopping cart
│       │   ├── products/            # Product listing
│       │   │   └── [slug]/          # Product details
│       │   ├── t/[...permalink]/    # Category pages
│       │   └── categories/          # Category overview
│       ├── (checkout)/              # Checkout layout (no header/footer)
│       │   ├── checkout/[id]/       # Checkout flow
│       │   └── order-placed/[id]/   # Order confirmation
│       └── (wholesale)/             # Opt-in B2B portal (gated — see Wholesale guide)
│           └── wholesale/
│               ├── page.tsx         # Trade catalog
│               ├── products/[slug]/ # Wholesale PDP
│               ├── cart/            # Wholesale cart
│               ├── quick-order/     # SKU quick-order form
│               ├── apply/           # Trade-account application
│               └── _components/     # Gate, header, sign-in wall, pending, guest browse
├── components/
│   ├── cart/                        # CartDrawer
│   ├── checkout/                    # AddressStep, DeliveryStep, PaymentStep, etc.
│   ├── layout/                      # Header, Footer, CountrySwitcher
│   ├── navigation/                  # Breadcrumbs
│   ├── products/                    # ProductCard, ProductGrid, Filters, MediaGallery, HiddenPricePrompt
│   └── search/                      # SearchBar
├── contexts/
│   ├── AuthContext.tsx              # Auth state
│   ├── CartContext.tsx              # Client-side cart state sync
│   ├── CheckoutContext.tsx          # Checkout flow state
│   ├── HiddenPricingContext.tsx     # Prices-hidden signal for wholesale guests
│   └── StoreContext.tsx             # Store/locale/currency state
├── hooks/
│   ├── useCarouselProducts.ts      # Product carousel data
│   └── useProductListing.ts        # Product listing with filters
└── lib/
    ├── analytics/                   # GTM integration
    ├── constants.ts                 # App constants
    ├── data/                        # Server Actions (call @spree/sdk directly)
    │   ├── addresses.ts             # Address CRUD
    │   ├── cart.ts                  # Cart operations
    │   ├── checkout.ts              # Checkout flow
    │   ├── cookies.ts               # Auth check helper
    │   ├── countries.ts             # Countries/regions
    │   ├── credit-cards.ts          # Payment methods
    │   ├── customer.ts              # Auth & profile
    │   ├── gift-cards.ts            # Gift cards
    │   ├── orders.ts                # Order history
    │   ├── payment.ts               # Payment processing
    │   ├── products.ts              # Product queries
    │   ├── categories.ts            # Categories
    │   ├── wholesale.ts             # Wholesale channel + catalog + quick-order
    │   ├── utils.ts                 # Shared helpers (actionResult, withFallback)
    │   └── …                        # One file per domain — see src/lib/data/
    ├── spree/                       # Spree integration (client, cookies, auth refresh, middleware, surface)
    ├── wholesale.ts                 # Approval check + volume-pricing helpers
    └── utils/                       # Client utilities
        ├── address.ts               # Address formatting
        ├── cookies.ts               # Cookie helpers
        ├── credit-card.ts           # Card formatting
        ├── path.ts                  # URL path helpers
        └── product-query.ts         # Product filter query builder

The (wholesale) route group is an opt-in B2B portal, off unless a wholesale channel is configured. Its gating, surfaces, and pricing modes are covered in the Wholesale Portal guide.

Authentication Flow

  1. User submits login form
  2. Server action calls @spree/sdk to authenticate
  3. JWT token is stored in an httpOnly cookie via src/lib/spree cookie helpers
  4. Subsequent requests use withAuthRefresh() which reads the token from cookies automatically
  5. Token is never accessible to client-side JavaScript
typescript
// src/lib/data/customer.ts
import { getClient, withAuthRefresh, setAccessToken, setRefreshToken } from '@/lib/spree'

export async function login(email: string, password: string) {
  const result = await getClient().auth.login({ email, password })
  await setAccessToken(result.token)
  await setRefreshToken(result.refresh_token)
  return { success: true, user: result.user }
}

export async function getCustomer() {
  return withAuthRefresh(async (options) => {
    return getClient().customer.get(options) // reads token from cookie
  })
}

Multi-Region

The storefront serves multiple countries, currencies, and languages from one deployment via /{country}/{locale} URL segments and an edge middleware that detects and persists the visitor's region. See the Multi-Region guide.

Server Actions

All data fetching is done through server actions in src/lib/data/. These call @spree/sdk directly, using src/lib/spree helpers for auth and locale:

typescript
// Products — use getLocaleOptions() for locale-aware reads
import { getProducts, getProduct, getProductFilters } from '@/lib/data/products'

const products = await getProducts({ limit: 12 })
const product = await getProduct('product-slug')
const filters = await getProductFilters()

// Cart — use getCartOptions()/requireCartId() for cart operations
import { getCart, addToCart, updateCartItem, removeCartItem } from '@/lib/data/cart'

const cart = await getCart()
await addToCart('var_xxx', 1)
await updateCartItem('li_xxx', 2)
await removeCartItem('li_xxx')

// Authentication — use withAuthRefresh() for authenticated endpoints
import { login, register, logout, getCustomer } from '@/lib/data/customer'

await login('[email protected]', 'password')
const customer = await getCustomer()
await logout()

// Addresses — use withAuthRefresh() for customer data
import { getAddresses, createAddress, updateAddress, deleteAddress } from '@/lib/data/addresses'

const addresses = await getAddresses()
await createAddress({ first_name: 'John', ... })