Saleor “Paper” Storefront Audit

Codebase Stats

MetricValue
Total files in src/316
Lines of code (TS/TSX/JS/JSX)~24,500
Source locationsrc/ (Next.js App Router)
FrameworkNext.js 16.1.2, React 19.1.2
Package managerpnpm 10.28.1
UI foundationRadix UI + shadcn-style primitives
StylingTailwind CSS 3.4 + OKLCH CSS custom properties

Page Routes

RouteDescription
/[channel]Homepage — featured products grid
/[channel]/productsProduct listing page with pagination, filtering, sorting
/[channel]/products/[slug]Product detail page with gallery, variants, add-to-cart, JSON-LD
/[channel]/categories/[slug]Category page — hero banner + filtered product grid
/[channel]/collections/[slug]Collection page — hero banner + filtered product grid
/[channel]/search?query=Search results page with sort and pagination
/[channel]/cartFull cart page with line items, totals, checkout link
/[channel]/loginLogin page (also handles set-password via query params)
/[channel]/signupRegistration page
/[channel]/accountAccount overview — recent orders + default address
/[channel]/account/ordersPaginated order history
/[channel]/account/addressesSaved addresses with add/edit/delete/set-default
/[channel]/account/settingsProfile settings — edit name, change password, delete account
/[channel]/pages/[slug]CMS content pages (EditorJS)
/checkout?checkout=IDMulti-step checkout flow (separate layout)
POST /api/auth/registerAccount registration
POST /api/auth/reset-passwordPassword reset request
POST /api/auth/set-passwordSet new password
GET/POST /api/revalidateWebhook-driven cache revalidation
GET /api/ogDynamic OpenGraph image generation

Commerce Components

Product Card / Product List

ComponentPathLines
ProductCardsrc/ui/components/plp/product-card.tsx145
ProductGridsrc/ui/components/plp/product-grid.tsx15
ProductListsrc/ui/components/product-list.tsx
CategoryHerosrc/ui/components/plp/category-hero.tsx83
PageHeadersrc/ui/components/plp/page-header.tsx44

Cart

ComponentPathLines
CartDrawersrc/ui/components/cart/cart-drawer.tsx372
CartDrawerWrappersrc/ui/components/cart/cart-drawer-wrapper.tsx20
CartButtonsrc/ui/components/cart/cart-button.tsx35
CartContextsrc/ui/components/cart/cart-context.tsx41
Cart full pagesrc/app/[channel]/(main)/cart/page.tsx149

Checkout (multi-step)

ComponentPathLines
SaleorCheckout (orchestrator)src/checkout/views/saleor-checkout/saleor-checkout.tsx151
InformationStepsrc/checkout/views/saleor-checkout/information-step.tsx494
ShippingStepsrc/checkout/views/saleor-checkout/shipping-step.tsx218
PaymentStepsrc/checkout/views/saleor-checkout/payment-step.tsx450
ConfirmationStepsrc/checkout/views/saleor-checkout/confirmation-step.tsx123
ExpressCheckoutsrc/checkout/components/express-checkout/express-checkout.tsx70
AddressCard / AddressSelectorsrc/checkout/components/shipping-address/~6 files
GuestContact / SignInFormsrc/checkout/components/contact/~5 files

Variant Picker

ComponentPathLines
VariantSelectionSectionsrc/ui/components/pdp/variant-selection/variant-selection-section.tsx
VariantSelectorsrc/ui/components/pdp/variant-selection/variant-selector.tsx
ColorSwatchOptionsrc/ui/components/pdp/variant-selection/renderers/
SizeButtonOptionsrc/ui/components/pdp/variant-selection/renderers/
VariantSectionDynamicsrc/ui/components/pdp/variant-section-dynamic.tsx190
Total pdp directorysrc/ui/components/pdp/565

Faceted Filtering

ComponentPathLines
FilterBarsrc/ui/components/plp/filter-bar.tsx487
useProductFilterssrc/ui/components/plp/use-product-filters.ts279
filter-utils.tssrc/ui/components/plp/filter-utils.ts252
Total plp directorysrc/ui/components/plp/1,912
ComponentPathLines
SearchBarsrc/ui/components/nav/components/search-bar.tsx36
SearchResultssrc/ui/components/search-results.tsx
Search providersrc/lib/search/saleor-provider.ts103

Account

ComponentPathLines
AccountNavsrc/ui/components/account/account-nav.tsx107
OrderRowsrc/ui/components/account/order-row.tsx72
OrderTimelinesrc/ui/components/account/order-timeline.tsx122
AddressFormDialogsrc/ui/components/account/address-form-dialog.tsx200
EditNameFormsrc/ui/components/account/edit-name-form.tsx102
ChangePasswordFormsrc/ui/components/account/change-password-form.tsx151
DeleteAccountSectionsrc/ui/components/account/delete-account-section.tsx81
Total accountsrc/ui/components/account/1,085
ComponentPathLines
Headersrc/ui/components/header.tsx74
Footersrc/ui/components/footer.tsx218
NavLinkssrc/ui/components/nav/components/nav-links.tsx60
MobileMenusrc/ui/components/nav/components/mobile-menu.tsx68

Other Commerce UI

ComponentPathLines
AddToCartsrc/ui/components/pdp/add-to-cart.tsx90
StickyBar (mobile ATC)src/ui/components/pdp/sticky-bar.tsx82
ProductGallerysrc/ui/components/pdp/product-gallery.tsx33
ImageCarousel (Embla)src/ui/components/ui/image-carousel.tsx194
Breadcrumbssrc/ui/components/breadcrumbs.tsx
Paginationsrc/ui/components/pagination.tsx

UI Primitives (shadcn-style)

src/ui/components/ui/: accordion, badge, button, carousel, checkbox, dropdown-menu, image-carousel, input, label, sheet.

Feature Inventory

Faceted Filtering

Full faceted filtering via the FilterBar component (487 lines):

  • Price range: Predefined buckets (Under 50-100, 200+). Server-side via ProductFilterInput.price.
  • Categories: Server-side filtered by slug-to-ID resolution.
  • Colors: Client-side filtered from product attributes.
  • Sizes: Client-side filtered from product attributes.
  • Sort: Featured, Newest, Price asc/desc, Bestselling.
  • Active filter chips with remove buttons.
  • Mobile filter sheet: Responsive slide-out panel.

Available on /products, /categories/[slug], /collections/[slug].

Search

Built-in Saleor GraphQL search. No Algolia, MeiliSearch, or Typesense.

  • Provider: src/lib/search/saleor-provider.ts uses SearchProductsDocument query.
  • Code comment: “Uses Saleor’s built-in GraphQL search for the demo. Replace this with Typesense/Algolia/Meilisearch for production.”
  • No autocomplete — form submits to search results page on Enter.
  • Sort: relevance, price-asc, price-desc, name, newest.
  • Cursor-based pagination.

Checkout

Multi-step checkout (3-4 steps depending on product type):

  1. Information — email/contact + shipping address
  2. Shipping — shipping method selection (skipped for digital)
  3. Payment — billing address + payment method (dummy gateway: mirumee.payments.dummy)
  4. Confirmation — order summary

Client-side SPA using urql for GraphQL. URL-based step navigation.

Express Checkout

Decorative only. Apple Pay and Google Pay buttons render with icons but are explicitly marked as non-functional. Comment: “Currently decorative - signals that express checkout is possible.”

Full carousel on PDP using Embla Carousel (194 lines):

  • Horizontal swipe on mobile
  • Arrow navigation on desktop (hover-reveal)
  • Thumbnail strip on desktop
  • Dot indicators on mobile
  • Variant-specific image switching
  • No zoom/lightbox

Customer Accounts

Fully implemented:

  • Login (email/password via @saleor/auth-sdk)
  • Register (via API route)
  • Password reset (full email flow, enumeration-protected)
  • Order history (paginated, status badges, timeline)
  • Saved addresses (full CRUD, set default shipping/billing)
  • Profile settings (edit name, change password, delete account)
  • Account overview (dashboard with recent orders + default address)

SEO

  • JSON-LD: Product structured data (schema.org/Product) with pricing, availability, brand.
  • OG tags: Full OpenGraph + Twitter cards on all pages.
  • Dynamic OG images: Via /api/og endpoint.
  • Meta tags: Per-page generateMetadata with seoTitle/seoDescription.
  • Canonical URLs: Set via alternates.canonical.
  • robots.txt: Configured in root metadata.
  • Sitemap: Not present.

Theming / Branding

  • CSS custom properties in src/styles/brand.css using OKLCH color space.
  • Tailwind CSS consuming CSS variables (bg-background, text-foreground, etc.).
  • Dark mode via .dark class with complete token overrides.
  • Brand config in src/config/brand.ts (site name, copyright, tagline, social).
  • Geist font via Next.js font system.
  • Reskin difficulty: Easy. Change CSS vars in brand.css (~120 lines) for colors, update brand.ts for text.

API Integration

GraphQL Client

Two patterns:

  1. Server-side: Direct fetch() with executePublicGraphQL / executeAuthenticatedGraphQL in src/lib/graphql.ts (503 lines). Request queue with rate limiting, retry with exponential backoff, typed error hierarchy.
  2. Client-side (checkout): urql GraphQL client with @saleor/auth-sdk.

Queries / Mutations

  • Storefront: src/graphql/*.graphql (30+ files) → codegen to src/gql/
  • Checkout: src/checkout/graphql/*.graphql (4 files) → codegen to src/checkout/graphql/generated/
  • API routes: Inline GraphQL strings

Authentication

  • @saleor/auth-sdk with cookie-based token storage (access + refresh)
  • Server-side reads cookies via Next.js cookies() API
  • Short-lived access tokens (~15min), longer-lived refresh tokens

Backend Extensibility (Saleor Apps)

Saleor extends via Saleor Apps — standalone web services that register with the Saleor instance.

  • Architecture: Each App is a separate web application (typically Next.js)
  • Communication: Bidirectional — Apps call Saleor GraphQL, Saleor calls Apps via webhooks
  • Product lifecycle hooks: PRODUCT_CREATED, PRODUCT_UPDATED, PRODUCT_DELETED (async webhooks). Synchronous webhooks can modify/reject during creation.
  • Deployment: Self-hosted as standalone service with its own URL. Requires manifest registration, app tokens with scoped permissions.
  • Each integration = separate deployed service.