dist/codex/spree-commerce/skills/spree-headless-storefront/SKILL.md
Build with Spree's headless Next.js storefront — the official `spree/storefront` repo (Next.js 16 App Router with Server Actions and Turbopack, React 19 Server Components, Tailwind CSS 4, TypeScript 5, `@spree/sdk`, Sentry), server-only auth (httpOnly JWT cookies + publishable key), MeiliSearch faceted catalog, one-page checkout with Apple/Google Pay/Klarna/Affirm/SEPA, multi-region market routing, GA4 + JSON-LD SEO, and Vercel/Docker deployment. Use when forking or customizing the storefront, or evaluating headless adoption.
npx skillsauth add orcaqubits/agentic-commerce-claude-plugins spree-headless-storefrontInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Fetch live docs:
| Repo | Role |
|------|------|
| spree/spree-starter | Rails backend application |
| spree/storefront | Next.js headless storefront |
The storefront talks to the backend exclusively over API v3 (Store API).
| Layer | Tech |
|-------|------|
| Framework | Next.js 16 (App Router, Server Actions, Turbopack) |
| UI runtime | React 19 (Server Components + Client Components) |
| Styling | Tailwind CSS 4 |
| Type system | TypeScript 5 |
| API client | @spree/sdk with Zod schemas |
| Search | MeiliSearch faceted search |
| Auth | Server-only — httpOnly JWT cookies + publishable key |
| Payments | Stripe Elements / Apple Pay / Google Pay / Link / Klarna / Affirm / SEPA |
| Analytics | GA4, JSON-LD, OpenGraph |
| Error tracking | Sentry |
| Deploy | Vercel (recommended) or Docker |
pk_…) from adminstorefront/
├── app/
│ ├── (storefront)/ # public pages (App Router groups)
│ │ ├── [region]/[locale]/
│ │ │ ├── products/
│ │ │ ├── cart/
│ │ │ └── checkout/
│ │ └── account/
│ ├── api/ # Route Handlers for webhooks etc.
│ ├── layout.tsx
│ └── page.tsx
├── components/
│ ├── ui/ # Tailwind-styled primitives
│ ├── product/
│ ├── cart/
│ └── checkout/
├── lib/
│ ├── spree.server.ts # admin/auth client — never imported client-side
│ ├── spree.public.ts # publishable-key client
│ ├── auth.ts # Server Action helpers for sign in/out
│ └── meilisearch.ts
├── public/
├── tailwind.config.ts
├── next.config.ts
└── package.json
pk_…) is the only Spree credential that may reach the browser.order_token also in httpOnly cookie.// app/api/auth/sign-in/route.ts (Route Handler)
import { cookies } from 'next/headers';
import { spreePublic } from '@/lib/spree.public';
export async function POST(req: Request) {
const { email, password } = await req.json();
const { access_token, refresh_token, expires_in } = await spreePublic.auth.signIn({ email, password });
cookies().set('spree_jwt', access_token, {
httpOnly: true,
secure: true,
sameSite: 'lax',
maxAge: expires_in,
});
cookies().set('spree_refresh', refresh_token, { httpOnly: true, secure: true, sameSite: 'lax' });
return Response.json({ ok: true });
}
URLs follow /[region]/[locale]/...:
/us/en/products/classic-tee/de/de/produkte/classic-teeThe storefront resolves current_market from the URL, then passes it to the API so backend filters by market-allowed payment methods, shipping methods, prices.
The storefront's PLP (Product Listing Page) issues a MeiliSearch query (or hits a Spree API v3 endpoint that proxies to MeiliSearch — verify the live pattern). Facets: brand, color, size, price range, taxon. Typo-tolerant.
ord_…Implemented as one-page with progressive disclosure in the official storefront.
// app/(storefront)/cart/actions.ts
'use server';
import { cookies } from 'next/headers';
import { revalidatePath } from 'next/cache';
import { spreePublic } from '@/lib/spree.public';
export async function addToCart(variantId: string, quantity = 1) {
const orderToken = cookies().get('spree_cart_token')?.value;
const result = await spreePublic.cart.addItem({ variantId, quantity, orderToken });
if (!orderToken) {
cookies().set('spree_cart_token', result.token, { httpOnly: true, secure: true, sameSite: 'lax' });
}
revalidatePath('/cart');
return result;
}
next-sitemap or similarnext/scriptview_item, add_to_cart, begin_checkout, purchase — fired client-side after Server Action completiongit clone https://github.com/spree/storefront my-storefront
cd my-storefront
cp .env.example .env.local
# Set:
# SPREE_API_URL=http://localhost:4000 (your Spree backend)
# NEXT_PUBLIC_SPREE_PUBLISHABLE_KEY=pk_test_...
# MEILISEARCH_HOST=http://localhost:7700
# MEILISEARCH_PUBLIC_KEY=...
npm install
npm run dev # http://localhost:3000
The App Router structure makes customization file-system-based — add or replace files under app/. For shared layout changes, edit the relevant layout.tsx.
Override Tailwind tokens in tailwind.config.ts:
export default {
theme: {
extend: {
colors: {
brand: { 500: '#0066ff', 600: '#0052cc' }
},
fontFamily: {
display: ['"Inter Display"', 'sans-serif']
}
}
}
}
For Tailwind 4 specifically, prefer the @theme directive in CSS:
@import 'tailwindcss';
@theme {
--color-brand-500: oklch(0.6 0.2 250);
--font-display: 'Inter Display', sans-serif;
}
// app/(storefront)/[region]/[locale]/about/page.tsx
export default async function AboutPage({ params }: { params: Promise<{ region: string; locale: string }> }) {
const { locale } = await params;
return <main>About {locale}</main>;
}
Next.js 16 wraps params in a Promise — await it.
If you've added a Metafield or extension field on the backend:
const product = await spreePublic.products.find('prod_...', { expand: ['metafields'] });
const launchDate = product.metafields?.my_app?.launch_date;
Augment SDK types via types/spree.d.ts if you want type safety.
| Page | Strategy |
|------|----------|
| Homepage | ISR — revalidate: 60 |
| Product detail | ISR — revalidate: 300 (5 min) |
| Cart | Dynamic — dynamic = 'force-dynamic' |
| Account | Dynamic |
| Order confirmation | Dynamic |
Vercel (recommended):
SPREE_API_URL, NEXT_PUBLIC_SPREE_PUBLISHABLE_KEY, MeiliSearch creds, Sentry DSN)npm run buildDocker:
Dockerfilespree.server.ts from a Client Component — Next.js's 'use server' enforcement is essential; opt into it.revalidatePath after Server Actions — stale UI.Always re-fetch the storefront repo's README before customizing — the codebase iterates rapidly with each Spree release.
tools
Build Spree extensions as Rails engines — gem scaffolding, `bin/rails g spree:extension`, mounting routes/migrations/assets, the modern `prepend` decorator pattern (`*_decorator.rb` with `self.prepended(base)`), generators (`spree:model_decorator`, `spree:controller_decorator`), the four customization surfaces in preference order (Events > Webhooks > Dependencies > Decorators), Spree::Dependencies for swapping service objects, gem release/versioning, and the deprecated Deface engine. Use when building a reusable Spree extension or adding non-trivial customization to an app.
development
Build with Spree's event bus and Webhooks 2.0 — `Spree::Events` publication, `Spree::Subscriber` DSL with `subscribes_to` and `on`, wildcard matching, lifecycle events (`{model}.created/.updated/.deleted` via `publishes_lifecycle_events`), the canonical event catalog (order.*, payment.*, shipment.*, product.*), Webhooks 2.0 endpoints, HMAC-SHA256 signing (`X-Spree-Webhook-Signature`), exponential-backoff retries, and Sidekiq job orchestration. Use when wiring event-driven business logic, building webhook consumers, or replacing ActiveSupport callback chains.
tools
Cross-cutting Spree development patterns — the customization preference hierarchy (Events > Webhooks > Dependencies > Decorators), `Spree::Dependencies` service-object swapping, the `_decorator.rb` + `prepend` + `self.prepended` idiom, idempotent subscribers and webhook receivers, multi-store scoping discipline, prefixed IDs, calculator polymorphism (shipping/promotion/tax share the base), service-object composition with `dry-monads` or simple results, why to avoid `class_eval` reopening and Deface, and Spree-on-Rails idioms (Hotwire/Turbo Stimulus, ActiveStorage, Action Cable, Sidekiq). Use when designing the architecture of a Spree extension or solving cross-cutting concerns.
development
Deploy Spree to production — PostgreSQL + Redis + Sidekiq stack, Docker multi-arch images on GHCR, the `spree-starter` Dockerfile + Compose, Heroku/Render/Fly.io/AWS targets, env-var conventions, RAILS_MASTER_KEY, asset precompilation (Tailwind 4 + Propshaft), Action Cable, MeiliSearch indexing, S3 / ActiveStorage for media, log/observability setup, zero-downtime deploys, and migration strategy. Use when going from local dev to production, scaling Spree, or troubleshooting deploys.