Hanzo
CommerceDeployment

Vercel

Deploy the Next.js storefront and docs to Vercel

Deploy your Next.js storefront and documentation site to Vercel with edge functions, ISR for product pages, and environment variable configuration.

Prerequisites

  • A Vercel account (vercel.com)
  • A Hanzo Commerce backend running (see Docker or Kubernetes)
  • Your storefront repository on GitHub, GitLab, or Bitbucket

Import the Project

npx vercel

Or import directly from the Vercel dashboard:

  1. Go to vercel.com/new
  2. Select your storefront repository
  3. Vercel auto-detects Next.js and configures the build

Environment Variables

Configure these environment variables in your Vercel project settings (Settings > Environment Variables):

VariableValueEnvironment
HANZO_API_KEYhk_live_...Production
HANZO_API_KEYhk_test_...Preview, Development
NEXT_PUBLIC_COMMERCE_URLhttps://commerce.example.comAll
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEYpk_live_...Production
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEYpk_test_...Preview, Development
STRIPE_SECRET_KEYsk_live_...Production
NEXT_PUBLIC_URLhttps://store.example.comProduction

Set them via the CLI:

vercel env add HANZO_API_KEY production
vercel env add HANZO_API_KEY preview
vercel env add NEXT_PUBLIC_COMMERCE_URL production

Edge Functions for API Proxy

Use Next.js Edge Runtime to proxy Commerce API requests with low latency. This keeps your API key server-side.

import { NextRequest, NextResponse } from 'next/server'

export const runtime = 'edge'

const COMMERCE_URL = process.env.NEXT_PUBLIC_COMMERCE_URL!
const API_KEY = process.env.HANZO_API_KEY!

export async function GET(
  req: NextRequest,
  { params }: { params: { path: string[] } }
) {
  const path = params.path.join('/')
  const url = new URL(req.url)

  const res = await fetch(`${COMMERCE_URL}/${path}${url.search}`, {
    headers: {
      Authorization: `Bearer ${API_KEY}`,
      'Content-Type': 'application/json',
    },
  })

  const data = await res.json()
  return NextResponse.json(data, { status: res.status })
}

export async function POST(
  req: NextRequest,
  { params }: { params: { path: string[] } }
) {
  const path = params.path.join('/')
  const body = await req.text()

  const res = await fetch(`${COMMERCE_URL}/${path}`, {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${API_KEY}`,
      'Content-Type': 'application/json',
    },
    body,
  })

  const data = await res.json()
  return NextResponse.json(data, { status: res.status })
}

ISR for Product Pages

Use Incremental Static Regeneration to serve product pages as static HTML while keeping them fresh:

import { commerce } from '@/lib/commerce'
import { notFound } from 'next/navigation'

// Revalidate product pages every 60 seconds
export const revalidate = 60

export async function generateStaticParams() {
  const { products } = await commerce.products.list({ limit: 200 })
  return products.map((p) => ({ slug: p.slug }))
}

export default async function ProductPage({
  params,
}: {
  params: { slug: string }
}) {
  const product = await commerce.products.getBySlug(params.slug)
  if (!product) notFound()

  return (
    <main>
      <h1>{product.name}</h1>
      <p>{(product.price / 100).toFixed(2)} {product.currency}</p>
    </main>
  )
}

On-Demand Revalidation

Trigger revalidation from Commerce webhooks when products are updated:

import { commerce } from '@/lib/commerce'
import { revalidatePath } from 'next/cache'
import { NextRequest, NextResponse } from 'next/server'

export async function POST(req: NextRequest) {
  const signature = req.headers.get('x-commerce-signature')!
  const body = await req.text()

  const event = commerce.webhooks.verify(body, signature)

  if (event.type === 'product.updated' || event.type === 'product.created') {
    revalidatePath(`/products/${event.data.slug}`)
    revalidatePath('/products')
  }

  return NextResponse.json({ revalidated: true })
}

Register the webhook in Commerce:

await commerce.webhooks.create({
  url: 'https://store.example.com/api/revalidate',
  events: ['product.created', 'product.updated', 'product.deleted'],
})

Serverless Function Limits

Be aware of Vercel's serverless function constraints:

LimitHobbyProEnterprise
Execution timeout10s60s900s
Body size4.5 MB4.5 MB4.5 MB
Memory1024 MB3008 MB3008 MB

Tips for staying within limits:

  • Use Edge Runtime for lightweight API proxying (no cold starts)
  • Paginate large product listings server-side
  • Stream large responses where possible
  • Offload heavy processing to Commerce backend

Custom Domain

Add your domain in Vercel project settings:

vercel domains add store.example.com

Update your DNS:

  • A record: 76.76.21.21
  • CNAME: cname.vercel-dns.com (for subdomains)

Vercel provisions SSL automatically.

Deploy

Push to your main branch to trigger a production deployment:

git push origin main

Or deploy manually:

vercel --prod

Preview Deployments

Every pull request gets a unique preview URL automatically. Preview deployments use the Preview environment variables (test API keys, sandbox Stripe).

Monitoring

  • Vercel Analytics: Enable in project settings for Web Vitals
  • Vercel Logs: Real-time function logs in the dashboard
  • Speed Insights: Automatic performance monitoring

Next Steps

How is this guide?

Last updated on

On this page