Project: Interactive E-commerce Catalog
In this project, we will build a product catalog catalog page. The page fetches products on the server, filters items dynamically based on URL search query parameters, and updates a shopping cart using Server Actions.
1. Project Specifications
- Search Parameters: Filters products by Category and Name using Next.js
searchParams. - Server-side Filtering: Database filtration is executed directly on the server to keep page bundles lightweight.
- Server Actions: Users can add products to a shopping cart session securely.
2. Implementing the Catalog Page
Create the component at src/app/catalog/page.tsx and paste the following implementation:
// src/app/catalog/page.tsx
import { revalidatePath } from "next/cache";
interface Product {
id: number;
name: string;
category: string;
price: number;
}
const mockProducts: Product[] = [
{ id: 1, name: "Mechanical Keyboard", category: "hardware", price: 89 },
{ id: 2, name: "Wired Gaming Mouse", category: "hardware", price: 49 },
{ id: 3, name: "Sleek Coffee Mug", category: "kitchen", price: 15 },
{ id: 4, name: "Cast Iron Skillet", category: "kitchen", price: 35 }
];
// Server Action helper to add items to cart
async function addToCartAction(formData: FormData) {
"use server";
const productId = formData.get("productId");
// Save to user session cookie or database store
console.log(`[ACTION] Item successfully added to cart: ${productId}`);
// Revalidate to show updated items count
revalidatePath("/catalog");
}
interface CatalogProps {
searchParams: Promise<{ query?: string; category?: string }>;
}
export default async function CatalogPage({ searchParams }: CatalogProps) {
const resolvedParams = await searchParams;
const query = resolvedParams.query || "";
const category = resolvedParams.category || "";
// Filter products based on URL parameters
const filteredProducts = mockProducts.filter((product) => {
const matchesQuery = product.name.toLowerCase().includes(query.toLowerCase());
const matchesCategory = category ? product.category === category : true;
return matchesQuery && matchesCategory;
});
return (
<div className="max-w-4xl mx-auto p-8">
<h1 className="text-3xl font-bold">Product Catalog</h1>
{/* Filter Options */}
<form method="GET" action="/catalog" className="flex gap-4 mt-6">
<input
name="query"
defaultValue={query}
placeholder="Search items..."
className="border p-2 rounded flex-1"
/>
<select name="category" defaultValue={category} className="border p-2 rounded">
<option value="">All Categories</option>
<option value="hardware">Hardware</option>
<option value="kitchen">Kitchen</option>
</select>
<button type="submit" className="bg-blue-600 text-white px-4 rounded">
Filter
</button>
</form>
{/* Catalog Grid */}
<div className="grid grid-cols-2 gap-6 mt-8">
{filteredProducts.map((product) => (
<div key={product.id} className="border p-6 rounded shadow-sm">
<h3 className="font-bold text-lg">{product.name}</h3>
<p className="text-gray-500 text-sm">Category: {product.category}</p>
<p className="text-xl font-semibold mt-2">${product.price}</p>
{/* Add to cart button utilizing a Server Action */}
<form action={addToCartAction} className="mt-4">
<input type="hidden" name="productId" value={product.id} />
<button
type="submit"
className="w-full bg-green-600 text-white py-2 rounded hover:bg-green-700"
>
Add to Cart
</button>
</form>
</div>
))}
</div>
</div>
);
}Since the search form uses a standard GET submission, clicking "Filter" updates the URL browser parameters automatically (for example, /catalog?query=keyboard&category=hardware), which triggers an on-demand server render with filtered results.
Published on Last updated: