01.software Docs

Product listing grid

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

Product listing grid

Prepare

Enable ecommerce, use a Publishable Key, and publish at least one product assigned to the active market. Start with Quickstart. Register an API origin for browser calls; this server example does not prove CORS admission.

These examples use the published SDK 0.65.1, Next.js 16.3.8 App Router, React 19.2.4, and Node 24.15.0. Install the SDK in your own app with npm install @01.software/sdk@0.65.1. The examples use force-dynamic; SDK calls run on requests, not at build time. This assumes no cacheComponents or static export. Compile and controlled local transport checks do not certify hosted availability or remote workspace admission.

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 creates card and swatch URLs for the detail recipe. Zero cards is a successful empty catalog. Product unlisted is omitted from listings but is reachable by direct detail; drafts, archived and not-yet-published products are not public. Keep the same market and content-locale context between listing and detail.

Credential, 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 shaped DTOs. availableForSale is a cached hint, not a stock quantity or reservation. Do not render stock, reservedStock, or derive available quantities from catalog data. stockSnapshot() gives a point-in-time overlay; stockCheck() checks requested quantities; checkout rechecks and owns final stock admission. None of these reads reserves stock.

Next Actions

Open a card or swatch in Product detail. Use Ecommerce for the full journey, SDK for query hooks, filtering and cache resources, and Commerce Operations for catalog ownership.

On this page