01.software Docs

Product listing grid

Render a product listing grid, empty catalog and safe card links.

Prepare

Enable ecommerce, use a Publishable Key, and publish at least one product assigned to the active market. Start with Quickstart. This example calls the SDK on the server. To call it from the browser, first allow your browser origin.

The example runs on each request in a Next.js App Router app. Build Options lists the tested versions and setup.

Render the listing grid

// app/products/page.tsx
import { createClient } from '@01.software/sdk'

export const dynamic = 'force-dynamic'

export default async function ProductsPage() {
  const client = createClient({
    publishableKey: process.env.NEXT_PUBLIC_SOFTWARE_PUBLISHABLE_KEY!,
    apiUrl: process.env.SOFTWARE_API_URL,
  })
  const page = await client.commerce.product.listingPage({
    limit: 24,
    basePath: '/products',
    emit: 'canonical-id',
  })
  if (page.cards.length === 0) return <p>No published products yet.</p>
  return (
    <ul>
      {page.cards.map((card) => (
        <li key={card.id}>
          <a href={card.href}>
            {typeof card.title === 'string'
              ? card.title
              : (card.title.en ?? card.title.ko ?? 'Untitled')}
          </a>
          <span>{card.priceRange.minPrice}</span>
          <ul aria-label="Options">
            {card.swatches.map((swatch) => (
              <li key={swatch.optionValueId}>
                <a href={swatch.href}>{swatch.label}</a>
              </li>
            ))}
          </ul>
        </li>
      ))}
    </ul>
  )
}

Normal, empty, and failure results

Visit /products. listingPage() paginates products, not colorways, and builds card and swatch URLs for the detail recipe. Zero cards is a successful empty catalog. unlisted products are left out of the listing but open from a direct detail URL. Draft, archived, and not-yet-published products are not public. Use the same market and content locale for listing and detail.

Key, feature, workspace, and network failures throw. Do not catch them and return an empty list. Copy this read retry boundary:

// app/products/error.tsx
'use client'

export default function ProductError({ reset }: { reset: () => void }) {
  return (
    <div role="alert">
      Catalog unavailable. <button onClick={reset}>Retry read</button>
    </div>
  )
}

Public stock boundary

Catalog cards and detail are public product data. availableForSale is a cached hint, not a stock quantity or a reservation. Do not show stock or reservedStock, and do not derive available quantities from catalog data. stockSnapshot() returns stock at one point in time, and stockCheck() checks requested quantities. Checkout checks stock again and makes the final decision. These catalog and stock reads do not reserve stock.

Next Actions

Open a card or swatch in Product detail. See Ecommerce for the full purchase, SDK import boundaries for query hooks, Storefront cache resources for cache invalidation, and Commerce Operations for catalog ownership.

On this page