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 vercelOr import directly from the Vercel dashboard:
- Go to vercel.com/new
- Select your storefront repository
- Vercel auto-detects Next.js and configures the build
Environment Variables
Configure these environment variables in your Vercel project settings (Settings > Environment Variables):
| Variable | Value | Environment |
|---|---|---|
HANZO_API_KEY | hk_live_... | Production |
HANZO_API_KEY | hk_test_... | Preview, Development |
NEXT_PUBLIC_COMMERCE_URL | https://commerce.example.com | All |
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY | pk_live_... | Production |
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY | pk_test_... | Preview, Development |
STRIPE_SECRET_KEY | sk_live_... | Production |
NEXT_PUBLIC_URL | https://store.example.com | Production |
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 productionEdge 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:
| Limit | Hobby | Pro | Enterprise |
|---|---|---|---|
| Execution timeout | 10s | 60s | 900s |
| Body size | 4.5 MB | 4.5 MB | 4.5 MB |
| Memory | 1024 MB | 3008 MB | 3008 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.comUpdate 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 mainOr deploy manually:
vercel --prodPreview 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
- Set up the Commerce backend with Docker or Kubernetes
- Add multi-currency support with region detection via Vercel headers
- Review the Next.js storefront recipe for the full implementation
How is this guide?
Last updated on