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',
})Product Search
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