Hanzo
CommerceStorefront Development

Products

Display products, collections, and search results in your storefront

This guide covers fetching and displaying products, collections, and search results in your storefront application.

List Products

// Fetch paginated products
const { data: products, pagination } = await commerce.products.list({
  limit: 20,
  offset: 0,
})

With Filters

const { data: products } = await commerce.products.list({
  collection: 'apparel',
  minPrice: 1000,
  maxPrice: 5000,
  sort: 'price:asc',
  limit: 20,
})

Get a Single Product

// By ID
const product = await commerce.products.get('prod_abc123')

// By slug (preferred for URLs)
const product = await commerce.products.getBySlug('premium-t-shirt')

Collections

Collections group products for merchandising. Use them for category pages, featured sections, and sale pages.

// List all collections
const collections = await commerce.collections.list()

// Get products in a collection
const { data: products } = await commerce.products.list({
  collection: 'summer-sale',
})

Full-text search across product names, descriptions, and tags:

const { data: results } = await commerce.products.search({
  query: 'cotton t-shirt',
  limit: 20,
})

Variant Selection

Products with options generate variants. Build a variant selector UI:

function VariantSelector({ product }: { product: Product }) {
  const [selected, setSelected] = useState<Record<string, string>>({})

  // Find the variant matching selected options
  const variant = product.variants.find(v =>
    Object.entries(selected).every(([key, val]) => v.options[key] === val)
  )

  return (
    <div>
      {product.options.map(option => (
        <div key={option.name}>
          <label>{option.name}</label>
          <select
            value={selected[option.name] || ''}
            onChange={e => setSelected(prev => ({
              ...prev, [option.name]: e.target.value,
            }))}
          >
            {option.values.map(v => (
              <option key={v} value={v}>{v}</option>
            ))}
          </select>
        </div>
      ))}
      {variant && (
        <p>Price: ${(variant.price / 100).toFixed(2)}</p>
      )}
    </div>
  )
}

Product Images

function ProductGallery({ images }: { images: ProductImage[] }) {
  const [active, setActive] = useState(0)

  return (
    <div>
      <img
        src={images[active].url}
        alt={images[active].alt}
        width={600}
        height={600}
      />
      <div className="flex gap-2 mt-4">
        {images.map((img, i) => (
          <button key={i} onClick={() => setActive(i)}>
            <img src={img.url} alt={img.alt} width={80} height={80} />
          </button>
        ))}
      </div>
    </div>
  )
}

Server-Side Rendering

For Next.js App Router, fetch products in server components for SEO:

// app/products/page.tsx
export default async function ProductsPage({
  searchParams,
}: {
  searchParams: { page?: string; collection?: string }
}) {
  const page = parseInt(searchParams.page || '1')
  const { data: products, pagination } = await commerce.products.list({
    collection: searchParams.collection,
    limit: 20,
    offset: (page - 1) * 20,
  })

  return (
    <div>
      <ProductGrid products={products} />
      <Pagination total={pagination.total} current={page} perPage={20} />
    </div>
  )
}

Use commerce.products.getBySlug() for product detail pages so your URLs are human-readable (e.g. /products/premium-t-shirt).

How is this guide?

Last updated on

On this page