Saleor Next.js Storefront
Before writing code
Fetch live docs:
- Fetch
https://docs.saleor.io/docs/developer/storefront for storefront development guide
- Web-search
site:docs.saleor.io storefront GraphQL client setup for client configuration
- Web-search
site:github.com saleor/storefront nextjs for latest storefront starter source
- Web-search
site:docs.saleor.io checkout flow storefront for checkout integration
- Web-search
site:docs.saleor.io channel storefront routing for multi-channel setup
Storefront Architecture
Saleor storefronts are headless -- they consume the GraphQL API over HTTP:
| Layer |
Component |
| Frontend |
Next.js App Router (SSR / RSC) |
| GraphQL Client |
urql, Apollo Client, or graphql-request |
| API |
Saleor GraphQL endpoint (/graphql/) |
| Styling |
Tailwind CSS (official starter) |
| Deployment |
Vercel, Netlify, or any Node.js host |
GraphQL Client Options
| Client |
Strengths |
Best For |
urql |
Lightweight, extensible, SSR-friendly |
Official starter default |
Apollo Client |
Mature ecosystem, normalized cache |
Complex caching needs |
graphql-request |
Minimal, no framework dependency |
Simple server-side fetching |
fetch (raw) |
Zero dependencies |
One-off queries in server components |
Client Setup Pattern
// lib/graphql-client.ts
// Fetch live docs for current client initialization
import { createClient, cacheExchange, fetchExchange } from "urql"
export const client = createClient({
url: process.env.NEXT_PUBLIC_SALEOR_API_URL!,
exchanges: [cacheExchange, fetchExchange],
})
Environment Variables
| Variable |
Purpose |
NEXT_PUBLIC_SALEOR_API_URL |
Saleor GraphQL endpoint URL |
SALEOR_API_URL |
Server-side only API URL (if different) |
NEXT_PUBLIC_DEFAULT_CHANNEL |
Default channel slug |
NEXT_PUBLIC_STOREFRONT_URL |
Public storefront URL (for SEO) |
Channel-Based URL Routing
Saleor channels enable multi-region storefronts from a single instance:
| URL Pattern |
Channel |
Currency |
/en-us/products |
default-channel |
USD |
/en-gb/products |
channel-uk |
GBP |
/de/products |
channel-de |
EUR |
Routing Strategy
| Approach |
Implementation |
| Path prefix |
/[channel]/products in Next.js App Router |
| Subdomain |
us.store.com, uk.store.com with middleware |
| Cookie/header |
Single URL, channel detected from locale preference |
The channel slug is passed as an argument to every GraphQL query that returns channel-scoped data (products, pricing, collections).
Key Storefront Pages
| Page |
Route |
Data Source |
| Homepage |
/ |
Featured products, collections |
| Product listing |
/products |
products(channel, first, filter) query |
| Product detail |
/products/[slug] |
product(slug, channel) query |
| Collection |
/collections/[slug] |
collection(slug, channel) query |
| Category |
/categories/[slug] |
category(slug) + products query |
| Cart |
/cart |
Local state + cart GraphQL object |
| Checkout |
/checkout |
checkout query and mutations |
| Account |
/account |
me query (authenticated) |
| Order history |
/account/orders |
me { orders } query |
| Search |
/search |
products(filter: {search}) query |
Server vs Client Component Split
| Pattern |
Use For |
| Server Component (RSC) |
Product listing, product detail, static content, SEO metadata |
| Client Component |
Cart drawer, quantity selector, add-to-cart button, checkout form |
| Server Action |
Cart mutations, checkout steps, address submission |
| Route Handler |
Webhook receivers, revalidation triggers |
Data Fetching in Server Components
// app/products/[slug]/page.tsx
// Fetch live docs for current query patterns
export default async function ProductPage({ params }) {
const { product } = await executeQuery(ProductBySlugDocument, {
slug: params.slug, channel: DEFAULT_CHANNEL,
})
return <ProductTemplate product={product} />
}
Checkout Flow
| Step |
User Action |
GraphQL Operation |
| 1. Create checkout |
First add-to-cart |
checkoutCreate mutation |
| 2. Add lines |
Add products to cart |
checkoutLinesAdd mutation |
| 3. Update lines |
Change quantity |
checkoutLinesUpdate mutation |
| 4. Set email |
Enter email |
checkoutEmailUpdate mutation |
| 5. Shipping address |
Enter address |
checkoutShippingAddressUpdate mutation |
| 6. Billing address |
Enter or same as shipping |
checkoutBillingAddressUpdate mutation |
| 7. Select shipping |
Choose method |
checkoutDeliveryMethodUpdate mutation |
| 8. Payment |
Enter payment details |
transactionInitialize or gateway-specific |
| 9. Complete |
Confirm order |
checkoutComplete mutation |
Checkout ID is stored in a cookie for persistence across sessions and server-side access.
Caching Strategies
| Strategy |
Use Case |
Implementation |
| ISR |
Product pages |
revalidate in fetch options |
| On-demand |
After product update webhook |
revalidatePath / revalidateTag |
| Client cache |
Cart state |
urql/Apollo normalized cache |
| Static |
Homepage collections |
generateStaticParams |
| No cache |
Checkout, account |
cache: "no-store" in fetch |
SEO with Server Components
| SEO Aspect |
Implementation |
| Title and meta |
generateMetadata in page components |
| Open Graph |
Product images and descriptions in OG tags |
| Structured data |
JSON-LD Product schema in script tags |
| Sitemap |
Dynamic sitemap.xml from product/collection queries |
| Canonical URLs |
alternates.canonical in metadata |
| Robots |
robots.txt via Next.js convention |
Image Handling
| Aspect |
Detail |
| Source |
Saleor media URL from product image fields |
| Optimization |
Next.js <Image> component with remote patterns |
| Thumbnails |
Saleor generates thumbnails at configurable sizes |
| CDN |
Configure next.config.js remotePatterns for Saleor domain |
Best Practices
- Use Server Components for all data fetching -- minimize client-side JavaScript
- Store checkout ID in an HTTP-only cookie for security and SSR access
- Scope every storefront query with the
channel argument
- Use
generateStaticParams for product and collection pages for SEO and speed
- Implement on-demand revalidation via webhooks for real-time content updates
- Handle multi-channel at the layout or middleware level, not per-page
- Use GraphQL code generation for type safety across all queries and mutations
- Keep the checkout as a linear flow -- do not allow skipping steps
Fetch the Saleor storefront documentation for exact GraphQL query patterns, checkout mutation sequences, and channel routing strategies before implementing.
1---2name: saleor-storefront3description: Build Next.js storefronts for Saleor — GraphQL client setup, channel routing, Tailwind CSS, server components, checkout flow, and SEO. Use when developing Saleor storefronts.4---56# Saleor Next.js Storefront78## Before writing code910**Fetch live docs**:111. Fetch `https://docs.saleor.io/docs/developer/storefront` for storefront development guide122. Web-search `site:docs.saleor.io storefront GraphQL client setup` for client configuration133. Web-search `site:github.com saleor/storefront nextjs` for latest storefront starter source144. Web-search `site:docs.saleor.io checkout flow storefront` for checkout integration155. Web-search `site:docs.saleor.io channel storefront routing` for multi-channel setup1617## Storefront Architecture1819Saleor storefronts are headless -- they consume the GraphQL API over HTTP:2021| Layer | Component |22|-------|-----------|23| Frontend | Next.js App Router (SSR / RSC) |24| GraphQL Client | urql, Apollo Client, or graphql-request |25| API | Saleor GraphQL endpoint (`/graphql/`) |26| Styling | Tailwind CSS (official starter) |27| Deployment | Vercel, Netlify, or any Node.js host |2829## GraphQL Client Options3031| Client | Strengths | Best For |32|--------|-----------|----------|33| `urql` | Lightweight, extensible, SSR-friendly | Official starter default |34| `Apollo Client` | Mature ecosystem, normalized cache | Complex caching needs |35| `graphql-request` | Minimal, no framework dependency | Simple server-side fetching |36| `fetch` (raw) | Zero dependencies | One-off queries in server components |3738### Client Setup Pattern3940```typescript41// lib/graphql-client.ts42// Fetch live docs for current client initialization43import { createClient, cacheExchange, fetchExchange } from "urql"4445export const client = createClient({46 url: process.env.NEXT_PUBLIC_SALEOR_API_URL!,47 exchanges: [cacheExchange, fetchExchange],48})49```5051## Environment Variables5253| Variable | Purpose |54|----------|---------|55| `NEXT_PUBLIC_SALEOR_API_URL` | Saleor GraphQL endpoint URL |56| `SALEOR_API_URL` | Server-side only API URL (if different) |57| `NEXT_PUBLIC_DEFAULT_CHANNEL` | Default channel slug |58| `NEXT_PUBLIC_STOREFRONT_URL` | Public storefront URL (for SEO) |5960## Channel-Based URL Routing6162Saleor channels enable multi-region storefronts from a single instance:6364| URL Pattern | Channel | Currency |65|-------------|---------|----------|66| `/en-us/products` | `default-channel` | USD |67| `/en-gb/products` | `channel-uk` | GBP |68| `/de/products` | `channel-de` | EUR |6970### Routing Strategy7172| Approach | Implementation |73|----------|---------------|74| Path prefix | `/[channel]/products` in Next.js App Router |75| Subdomain | `us.store.com`, `uk.store.com` with middleware |76| Cookie/header | Single URL, channel detected from locale preference |7778The channel slug is passed as an argument to every GraphQL query that returns channel-scoped data (products, pricing, collections).7980## Key Storefront Pages8182| Page | Route | Data Source |83|------|-------|-------------|84| Homepage | `/` | Featured products, collections |85| Product listing | `/products` | `products(channel, first, filter)` query |86| Product detail | `/products/[slug]` | `product(slug, channel)` query |87| Collection | `/collections/[slug]` | `collection(slug, channel)` query |88| Category | `/categories/[slug]` | `category(slug)` + products query |89| Cart | `/cart` | Local state + cart GraphQL object |90| Checkout | `/checkout` | `checkout` query and mutations |91| Account | `/account` | `me` query (authenticated) |92| Order history | `/account/orders` | `me { orders }` query |93| Search | `/search` | `products(filter: {search})` query |9495## Server vs Client Component Split9697| Pattern | Use For |98|---------|---------|99| Server Component (RSC) | Product listing, product detail, static content, SEO metadata |100| Client Component | Cart drawer, quantity selector, add-to-cart button, checkout form |101| Server Action | Cart mutations, checkout steps, address submission |102| Route Handler | Webhook receivers, revalidation triggers |103104### Data Fetching in Server Components105106```typescript107// app/products/[slug]/page.tsx108// Fetch live docs for current query patterns109export default async function ProductPage({ params }) {110 const { product } = await executeQuery(ProductBySlugDocument, {111 slug: params.slug, channel: DEFAULT_CHANNEL,112 })113 return <ProductTemplate product={product} />114}115```116117## Checkout Flow118119| Step | User Action | GraphQL Operation |120|------|-------------|-------------------|121| 1. Create checkout | First add-to-cart | `checkoutCreate` mutation |122| 2. Add lines | Add products to cart | `checkoutLinesAdd` mutation |123| 3. Update lines | Change quantity | `checkoutLinesUpdate` mutation |124| 4. Set email | Enter email | `checkoutEmailUpdate` mutation |125| 5. Shipping address | Enter address | `checkoutShippingAddressUpdate` mutation |126| 6. Billing address | Enter or same as shipping | `checkoutBillingAddressUpdate` mutation |127| 7. Select shipping | Choose method | `checkoutDeliveryMethodUpdate` mutation |128| 8. Payment | Enter payment details | `transactionInitialize` or gateway-specific |129| 9. Complete | Confirm order | `checkoutComplete` mutation |130131Checkout ID is stored in a cookie for persistence across sessions and server-side access.132133## Caching Strategies134135| Strategy | Use Case | Implementation |136|----------|----------|----------------|137| ISR | Product pages | `revalidate` in `fetch` options |138| On-demand | After product update webhook | `revalidatePath` / `revalidateTag` |139| Client cache | Cart state | urql/Apollo normalized cache |140| Static | Homepage collections | `generateStaticParams` |141| No cache | Checkout, account | `cache: "no-store"` in fetch |142143## SEO with Server Components144145| SEO Aspect | Implementation |146|-----------|---------------|147| Title and meta | `generateMetadata` in page components |148| Open Graph | Product images and descriptions in OG tags |149| Structured data | JSON-LD `Product` schema in script tags |150| Sitemap | Dynamic `sitemap.xml` from product/collection queries |151| Canonical URLs | `alternates.canonical` in metadata |152| Robots | `robots.txt` via Next.js convention |153154## Image Handling155156| Aspect | Detail |157|--------|--------|158| Source | Saleor media URL from product image fields |159| Optimization | Next.js `<Image>` component with remote patterns |160| Thumbnails | Saleor generates thumbnails at configurable sizes |161| CDN | Configure `next.config.js` `remotePatterns` for Saleor domain |162163## Best Practices164165- Use Server Components for all data fetching -- minimize client-side JavaScript166- Store checkout ID in an HTTP-only cookie for security and SSR access167- Scope every storefront query with the `channel` argument168- Use `generateStaticParams` for product and collection pages for SEO and speed169- Implement on-demand revalidation via webhooks for real-time content updates170- Handle multi-channel at the layout or middleware level, not per-page171- Use GraphQL code generation for type safety across all queries and mutations172- Keep the checkout as a linear flow -- do not allow skipping steps173174Fetch the Saleor storefront documentation for exact GraphQL query patterns, checkout mutation sequences, and channel routing strategies before implementing.