Loading lessons...
Project: E-Commerce Store with ISR & Route Handlers
Project: E-Commerce Store with ISR & Route Handlers
In this project, you will build a scalable E-Commerce Product Catalog with shopping cart API Route Handlers, tag-based revalidation (revalidateTag), and static metadata.
Project Architecture
app/api/cart/route.ts: API Route Handler for cart item retrieval and addition.app/products/[id]/page.tsx: ISR product details page with tag-based caching.app/actions/cart.ts: Server Action invokingrevalidateTag('cart').
1. Cart API Route Handler (app/api/cart/route.ts)
import { NextRequest, NextResponse } from 'next/server';
let cartItems: { productId: string; quantity: number }[] = [];
export async function GET() {
return NextResponse.json({ items: cartItems });
}
export async function POST(request: NextRequest) {
const body = await request.json();
if (!body.productId) {
return NextResponse.json({ error: 'Missing productId' }, { status: 400 });
}
cartItems.push({ productId: body.productId, quantity: body.quantity || 1 });
return NextResponse.json({ success: true, items: cartItems }, { status: 201 });
}
2. Product Details Page with ISR Tags (app/products/[id]/page.tsx)
import { Metadata } from 'next';
import { addToCartAction } from '@/app/actions/cart';
type Props = {
params: Promise<{ id: string }>;
};
async function getProduct(id: string) {
// Fetch with 1-hour ISR revalidation + tag!
const res = await fetch(`https://api.example.com/products/${id}`, {
next: { revalidate: 3600, tags: ['products', `product-${id}`] },
});
return res.json();
}
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { id } = await params;
const product = await getProduct(id);
return {
title: `${product.title} - Buy Online`,
description: product.description,
};
}
export default async function ProductDetailsPage({ params }: Props) {
const { id } = await params;
const product = await getProduct(id);
return (
<main className="max-w-4xl mx-auto p-8 grid grid-cols-2 gap-8">
<img src={product.image} alt={product.title} className="rounded shadow" />
<div>
<h1 className="text-3xl font-bold">{product.title}</h1>
<p className="text-2xl text-green-600 font-bold mt-2">${product.price}</p>
<p className="mt-4 text-gray-600">{product.description}</p>
<form action={addToCartAction} className="mt-6">
<input type="hidden" name="productId" value={id} />
<button type="submit" className="bg-black text-white px-6 py-3 rounded font-bold">
Add to Cart
</button>
</form>
</div>
</main>
);
}
3. Server Action Invalidating Cart Cache (app/actions/cart.ts)
'use server';
import { revalidateTag } from 'next/cache';
export async function addToCartAction(formData: FormData) {
const productId = formData.get('productId') as string;
// Perform backend cart database update...
console.log('Added product to cart:', productId);
// Invalidate cart tags programmatically across the entire site!
revalidateTag('cart');
}
TL;DR
- Tag fetch calls in product pages with
next: { tags: ['products'] }. - Use
revalidateTag('cart')in Server Actions to refresh cart counters instantly. - Export
generateMetadatato optimize SEO title and meta tags per product dynamically.