Important
Work in progress, please check our progress here: https://github.com/orgs/spree/projects/3
A modern, headless e-commerce storefront built with Next.js 16, React 19, and the Spree Commerce API v3.
- Next.js 16 - App Router, Server Actions, Turbopack
- React 19 - Latest React with improved Server Components
- Tailwind CSS 4 - Utility-first styling
- TypeScript 5 - Full type safety
- Sentry - Error tracking and performance monitoring with source maps
- @spree/sdk - Official Spree Commerce SDK
- @spree/next - Server actions, caching, and cookie-based auth
- Server-First Architecture - All API calls happen server-side using Next.js Server Actions
- Secure Authentication - JWT tokens stored in httpOnly cookies (not localStorage)
- Product Catalog - Browse, search, filter products by categories and with faceted navigation
- Product Details - View product information with variant selection and images
- Shopping Cart - Add, update, and remove items with server-side state
- Multi-Step Checkout - Guest visitors and signed in users supported, multi-shipments supported natively, Coupon Codes, Gift Cards, Store Credit
- Stripe payments - native Stripe payment support with Stripe SDKs, PCI-Compliant, 3DS-Secure, use Credit Cards, Apple Pay, Google Pay, Klarna, Affirm, SEPA payments and all other payment methods provided by Spree Stripe integration
- Google Tag Mananager and Google Analytics 4 Ecommerce events tracking supported natively
- Customer Account - Full account management:
- Profile management
- Order history with detailed order view
- Address book (create, edit, delete)
- Gift Cards and Store Credit
- Saved payment methods
- Multi-Region Support - Country and currency switching via URL segments
- Responsive Design - Mobile-first Tailwind CSS styling
- Error Tracking - Sentry integration for both server-side and client-side error monitoring with source maps
This starter follows a server-first pattern:
- Server Actions (
src/lib/data/) - All API calls are made server-side - httpOnly Cookies - Auth tokens and cart tokens are stored securely
- No Client-Side API Calls - The Spree API key is never exposed to the browser
- Cache Revalidation - Uses Next.js cache tags for efficient updates
Browser → Server Action → Spree API
(with httpOnly cookies)
- Node.js 20+ (required for Next.js 16)
- A running Spree Commerce 5.4+
- Install dependencies:
npm install- Copy the environment file and configure:
cp .env.local.example .env.local- Update
.env.localwith your Spree API credentials:
SPREE_API_URL=http://localhost:3000
SPREE_PUBLISHABLE_KEY=your_publishable_api_key_hereNote: These are server-side only variables (no
NEXT_PUBLIC_prefix needed).
| Variable | Description | Default |
|---|---|---|
GTM_ID |
Google Tag Manager container ID (e.g. GTM-XXXXXXX) |
(disabled) |
SENTRY_DSN |
Sentry DSN for error tracking (e.g. https://[email protected]/0) |
(disabled) |
SENTRY_ORG |
Sentry organization slug (for source map uploads) | (none) |
SENTRY_PROJECT |
Sentry project slug (for source map uploads) | (none) |
SENTRY_AUTH_TOKEN |
Sentry auth token (for source map uploads in CI) | (none) |
SENTRY_SEND_DEFAULT_PII |
Send PII (IP addresses, cookies, user data) to Sentry server-side | false |
NEXT_PUBLIC_SENTRY_SEND_DEFAULT_PII |
Send PII to Sentry client-side | false |
Privacy note: PII collection is disabled by default. Only set
SENTRY_SEND_DEFAULT_PII/NEXT_PUBLIC_SENTRY_SEND_DEFAULT_PIItotrueif you have appropriate user consent or a privacy policy covering this data.
npm run devOpen http://localhost:3001 in your browser.
npm run build
npm startsrc/
├── app/
│ └── [country]/[locale]/ # Localized routes
│ ├── account/ # Customer account pages
│ │ ├── addresses/ # Address management
│ │ ├── credit-cards/ # Saved payment methods
│ │ ├── orders/ # Order history
│ │ │ └── [id]/ # Order details
│ │ └── profile/ # Profile settings
│ ├── cart/ # Shopping cart
│ ├── products/ # Product listing
│ │ └── [slug]/ # Product details
│ ├── t/[...permalink]/ # Taxon/category pages
│ └── taxonomies/ # Category overview
├── components/
│ ├── layout/ # Header, Footer, CountrySwitcher
│ ├── products/ # ProductCard, ProductGrid, Filters
│ └── search/ # SearchBar
├── contexts/
│ └── CartContext.tsx # Client-side cart state sync
└── lib/
├── spree.ts # SDK client configuration
└── data/ # Server Actions
├── addresses.ts # Address CRUD operations
├── cart.ts # Cart operations
├── cookies.ts # Auth token management
├── countries.ts # Countries/regions list
├── credit-cards.ts # Payment methods
├── customer.ts # Auth & profile
├── orders.ts # Order history
├── products.ts # Product queries
├── store.ts # Store configuration
└── taxonomies.ts # Categories/taxons
All data fetching is done through server actions in src/lib/data/:
// Products
import { getProducts, getProduct, getProductFilters } from '@/lib/data/products'
const products = await getProducts({ per_page: 12 })
const product = await getProduct('product-slug', { includes: 'variants,images' })
const filters = await getProductFilters({ taxon_id: 'txn_xxx' })
// Cart
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
import { login, register, logout, getCustomer } from '@/lib/data/customer'
const result = await login('[email protected]', 'password')
const customer = await getCustomer()
await logout()
// Addresses
import { getAddresses, createAddress, updateAddress, deleteAddress } from '@/lib/data/addresses'
const addresses = await getAddresses()
await createAddress({ firstname: 'John', ... })- User submits login form
- Server action calls Spree API with credentials
- JWT token is stored in an httpOnly cookie
- Subsequent requests include the token automatically
- Token is never accessible to client-side JavaScript
// src/lib/data/cookies.ts
export async function setAuthToken(token: string) {
const cookieStore = await cookies()
cookieStore.set('_spree_jwt', token, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict',
})
}The storefront supports multiple countries and currencies via URL segments:
/us/en/products # US store, English
/de/de/products # German store, German
/uk/en/products # UK store, English
Use the CountrySwitcher component to change regions.
The storefront uses Tailwind CSS. Customize the design by modifying:
tailwind.config.ts- Theme configurationsrc/app/globals.css- Global styles
All components are in src/components/ and can be customized or replaced as needed.
To customize API behavior, modify the server actions in src/lib/data/.
The easiest way to deploy is using Vercel:
- Push your code to GitHub
- Import the repository in Vercel
- Add environment variables:
SPREE_API_URLandSPREE_PUBLISHABLE_KEY(required)GTM_ID(optional — Google Tag Manager)SENTRY_DSN,SENTRY_ORG,SENTRY_PROJECT,SENTRY_AUTH_TOKEN(optional — for error tracking with readable stack traces)
- Deploy
BSD-3-Clause