Back to Blog
ยท12 min

Build an E-Commerce Platform with Next.js: Complete Starter Guide

Building an e-commerce platform is one of the most common web development projects, yet it remains one of the most complex. Between product catalogs, shopping carts, checkout flows, payment processing, order management, and inventory tracking, a production-ready e-commerce application requires dozens of interconnected features.

An e-commerce starter kit eliminates the months of setup by providing all of this infrastructure pre-built and ready to customize. This guide walks through building a complete e-commerce platform using Next.js 14, Stripe, and Supabase, with production-ready code examples at every step.

Core E-Commerce Architecture

Every e-commerce platform shares a common architecture regardless of scale:

  • Product catalog with categories, search, and filtering
  • Shopping cart with persistent state
  • Checkout flow with payment processing
  • Order management and tracking
  • User accounts with order history
  • Admin dashboard for inventory management

Our E-Commerce Starter template includes all of these features pre-built with Next.js 14, TypeScript, Tailwind CSS, Stripe payment processing, and Supabase for the database and authentication.

Product Catalog with Supabase

// lib/products.ts
import { supabase } from './supabase'
import { cache } from 'react'

export interface Product {
  id: string
  name: string
  slug: string
  description: string
  price: number
  images: string[]
  category: string
  inventory: number
  rating: number
  reviews: number
}

export const getProducts = cache(async (category?: string) => {
  let query = supabase.from('products').select('*')

  if (category && category !== 'all') {
    query = query.eq('category', category)
  }

  const { data } = await query.order('created_at', { ascending: false })
  return data as Product[]
})

export const getProduct = cache(async (slug: string) => {
  const { data } = await supabase
    .from('products')
    .select('*')
    .eq('slug', slug)
    .single()
  return data as Product | null
})

Server Component Product Listing

// app/products/page.tsx
import { getProducts } from '@/lib/products'
import { ProductCard } from './ProductCard'
import { CategoryFilter } from './CategoryFilter'

export default async function ProductsPage({
  searchParams,
}: {
  searchParams: { category?: string }
}) {
  const products = await getProducts(searchParams.category)

  return (
    <div className="mx-auto max-w-7xl px-4 py-8">
      <h1 className="text-3xl font-bold">All Products</h1>
      <CategoryFilter active={searchParams.category} />
      <div className="mt-8 grid gap-6 sm:grid-cols-2 lg:grid-cols-4">
        {products.map(product => (
          <ProductCard key={product.id} product={product} />
        ))}
      </div>
    </div>
  )
}

Shopping Cart with Zustand

// store/cart.ts
import { create } from 'zustand'
import { persist } from 'zustand/middleware'

interface CartItem {
  productId: string
  name: string
  price: number
  quantity: number
  image: string
}

interface CartStore {
  items: CartItem[]
  addItem: (item: CartItem) => void
  removeItem: (productId: string) => void
  updateQuantity: (productId: string, quantity: number) => void
  clearCart: () => void
  total: () => number
}

export const useCartStore = create<CartStore>()(
  persist(
    (set, get) => ({
      items: [],
      addItem: (item) => {
        const items = get().items
        const existing = items.find(i => i.productId === item.productId)
        if (existing) {
          set({
            items: items.map(i =>
              i.productId === item.productId
                ? { ...i, quantity: i.quantity + 1 }
                : i
            ),
          })
        } else {
          set({ items: [...items, item] })
        }
      },
      removeItem: (productId) => {
        set({ items: get().items.filter(i => i.productId !== productId) })
      },
      updateQuantity: (productId, quantity) => {
        set({
          items: get().items.map(i =>
            i.productId === productId ? { ...i, quantity } : i
          ),
        })
      },
      clearCart: () => set({ items: [] }),
      total: () => get().items.reduce((sum, i) => sum + i.price * i.quantity, 0),
    }),
    { name: 'cart-storage' }
  )
)

Stripe Checkout Integration

// app/api/checkout/route.ts
import { NextResponse } from 'next/server'
import Stripe from 'stripe'
import { auth } from '@/lib/auth'

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!)

export async function POST(req: Request) {
  const session = await auth()
  if (!session?.user) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
  }

  const { items } = await req.json()

  const lineItems = items.map((item: any) => ({
    price_data: {
      currency: 'usd',
      product_data: {
        name: item.name,
        images: [item.image],
      },
      unit_amount: Math.round(item.price * 100),
    },
    quantity: item.quantity,
  }))

  const checkout = await stripe.checkout.sessions.create({
    customer_email: session.user.email!,
    mode: 'payment',
    line_items: lineItems,
    success_url: `${process.env.NEXT_PUBLIC_APP_URL}/orders?success=true`,
    cancel_url: `${process.env.NEXT_PUBLIC_APP_URL}/cart`,
    metadata: { userId: session.user.id },
  })

  return NextResponse.json({ url: checkout.url })
}

Order Management

When payment succeeds, a webhook creates the order:

// app/api/webhooks/stripe/route.ts
case 'checkout.session.completed': {
  const checkoutSession = event.data.object as Stripe.Checkout.Session
  const userId = checkoutSession.metadata?.userId

  if (userId) {
    await prisma.order.create({
      data: {
        userId,
        email: checkoutSession.customer_email!,
        total: checkoutSession.amount_total! / 100,
        status: 'completed',
        items: {
          create: lineItems.map((item: any) => ({
            productId: item.productId,
            price: item.price,
            quantity: item.quantity,
          })),
        },
      },
    })
  }
  break
}

E-Commerce Templates Available

BreafIO offers over 200 templates including several e-commerce solutions. The E-Commerce Starter is our flagship e-commerce template with product management, cart, Stripe checkout, order tracking, and admin dashboard. Built with Next.js 14, TypeScript, Tailwind CSS, Supabase, and Stripe.

For multi-vendor marketplaces, the Multi-Vendor Marketplace template extends the base e-commerce platform with vendor onboarding, commission management, and Stripe Connect integration.

The Bottom Line

Building an e-commerce platform from scratch takes months. Product catalogs, cart state management, payment processing, and order management each require careful implementation. An e-commerce starter kit gives you all of this infrastructure pre-built, tested, and ready to customize. Focus on your product selection, pricing strategy, and customer experience โ€” not on rebuilding payment webhooks and cart logic.

Ready to Build?

Get started with our production-ready starter kits and ship your project faster.

Browse Starter Kits