Next.js
Mini Project – Ecommerce
Build a complete shopping experience with product pages, cart functionality, and checkout flow for NewsWave's merchandise store
Ecommerce represents one of Next.js' most powerful applications. Unlike simple content sites, shopping platforms require complex state management, dynamic routing, and seamless user experiences. The NewsWave team wants to launch a merchandise store where readers can buy branded items like t-shirts, mugs, and laptop stickers. Building ecommerce with Next.js gives you superpowers that traditional React apps struggle with. Server-side rendering makes product pages load instantly and rank well in search engines. Static generation pre-builds your entire catalog at build time. Dynamic routing handles thousands of products without creating individual files. Real ecommerce sites like Hacker News Shop and Dev.to Store use these same patterns. They need fast product listings, detailed item pages, shopping cart persistence, and smooth checkout flows. Your NewsWave store will implement these core features using modern Next.js patterns.Project Structure and Planning
Ecommerce projects need careful architecture before writing code. Unlike content sites with simple page hierarchies, shopping platforms involve multiple user journeys, shared state between components, and complex data relationships. The NewsWave store needs several key pages and features. A product catalog displays all available items with filtering and search. Individual product pages show detailed information, images, and purchase options. Shopping cart functionality persists across page navigation. A checkout process collects customer information and processes orders. Planning the folder structure prevents chaos later. Product data needs consistent formatting across components. Cart state requires global management so users don't lose items when navigating. Checkout forms need validation and error handling.We organized the ecommerce structure with dedicated pages for shopping, individual products, cart, and checkout. Components handle reusable UI elements while lib contains product data. Dynamic routing [slug] creates pages for each product automatically.
Product Data and Catalog
Ecommerce starts with product data structure. Unlike blog posts with simple text content, products need multiple properties like name, price, images, inventory levels, and categories. This data drives every part of your shopping experience. Creating a robust product schema prevents bugs and inconsistencies. Each product needs a unique identifier for routing, descriptive information for SEO, pricing for calculations, and inventory tracking for availability. Images require multiple sizes for different display contexts. Mock product data works perfectly for learning ecommerce patterns. Real stores often connect to databases or APIs, but the component logic remains identical. Your NewsWave store will feature branded merchandise that appeals to developers and tech enthusiasts.// lib/products.js - NewsWave merchandise data
export const products = [
{
id: 'newswave-hoodie',
slug: 'newswave-hoodie', // URL path for this product
name: 'NewsWave Developer Hoodie',
price: 4999, // Store prices in cents to avoid decimal issues
image: '/images/hoodie.jpg',
category: 'apparel',
description: 'Premium hoodie for developers who stay informed',
inStock: true,
inventory: 25
},
{
id: 'code-coffee-mug',
slug: 'code-coffee-mug',
name: 'Code & Coffee Mug',
price: 1899,
image: '/images/mug.jpg',
category: 'accessories',
description: 'Perfect mug for coding sessions and news reading',
inStock: true,
inventory: 50
}
]// app/shop/page.js - Main catalog page
import { products } from '@/lib/products'
import ProductCard from '@/components/ProductCard'
export default function ShopPage() {
return (
<div className="max-w-6xl mx-auto px-4 py-8">
<h1 className="text-3xl font-bold mb-8">NewsWave Store</h1>
<div className="grid md:grid-cols-3 gap-6">
{products.map(product => (
<ProductCard key={product.id} product={product} />
))}
</div>
</div>
)
}We created the product catalog with clean grid layout and attractive product cards. Each item displays key information like name, category, and pricing. The responsive grid adapts to different screen sizes automatically.
Dynamic Product Pages
Individual product pages provide detailed information and purchase options for each item. Unlike the catalog's summary cards, these pages need comprehensive descriptions, multiple images, size options, and prominent add-to-cart functionality. Next.js dynamic routing creates these pages automatically using the product slug. When users visit/shop/newswave-hoodie, Next.js extracts the slug parameter and loads the corresponding product data. This approach scales to thousands of products without creating individual page files.
The product page architecture balances information density with clean design. Users need enough details to make confident purchases without feeling overwhelmed. High-quality images, clear pricing, inventory status, and obvious purchase buttons drive conversion.
// app/shop/[slug]/page.js - Individual product pages
import { products } from '@/lib/products'
import { notFound } from 'next/navigation'
import AddToCart from '@/components/AddToCart'
export default function ProductPage({ params }) {
// Find product by slug from URL parameter
const product = products.find(p => p.slug === params.slug)
// Show 404 page if product doesn't exist
if (!product) {
notFound()
}
return (
<div className="max-w-4xl mx-auto px-4 py-8">
<div className="grid md:grid-cols-2 gap-8">
<div>
<img src={product.image} alt={product.name} />
</div>
<div>
<h1 className="text-3xl font-bold">{product.name}</h1>
<p className="text-2xl text-green-600 font-bold">
${(product.price / 100).toFixed(2)}
</p>
<p>{product.description}</p>
<AddToCart product={product} />
</div>
</div>
</div>
)
}// Generate static pages for all products at build time
export async function generateStaticParams() {
// Return array of slug parameters for Next.js to pre-build
return products.map(product => ({
slug: product.slug // Creates /shop/newswave-hoodie, /shop/code-coffee-mug, etc
}))
}
// Add metadata for each product page (SEO)
export async function generateMetadata({ params }) {
const product = products.find(p => p.slug === params.slug)
return {
title: `${product.name} - NewsWave Store`,
description: product.description
}
}We created dynamic product pages using Next.js routing with [slug] parameters. The generateStaticParams function pre-builds all product pages for lightning-fast loading. Each page includes detailed product information and purchase options.
Shopping Cart Implementation
Shopping cart functionality represents the most complex part of ecommerce development. Unlike simple page navigation, carts require persistent state management, quantity calculations, and synchronization across multiple components. Users expect their selected items to survive page refreshes and navigation. React Context provides elegant cart state management without external libraries. The cart context wraps your entire application, making cart data and update functions available everywhere. This approach feels natural and integrates seamlessly with Next.js server components. Cart persistence enhances user experience dramatically. Nobody wants to lose their selected items when accidentally refreshing the page. Local storage maintains cart contents across browser sessions, while session storage clears when users close the tab.// components/CartProvider.js - Global cart state management
'use client'
import { createContext, useContext, useReducer, useEffect } from 'react'
const CartContext = createContext()
function cartReducer(state, action) {
switch (action.type) {
case 'ADD_ITEM':
// Check if item already exists in cart
const existingItem = state.items.find(item => item.id === action.product.id)
if (existingItem) {
// Increase quantity of existing item
return {
...state,
items: state.items.map(item =>
item.id === action.product.id
? { ...item, quantity: item.quantity + 1 }
: item
)
}
}
// Add new item to cart
return {
...state,
items: [...state.items, { ...action.product, quantity: 1 }]
}
case 'REMOVE_ITEM':
return {
...state,
items: state.items.filter(item => item.id !== action.productId)
}
default:
return state
}
}// CartProvider component with local storage persistence
export function CartProvider({ children }) {
const [cart, dispatch] = useReducer(cartReducer, { items: [] })
// Load cart from localStorage on initial render
useEffect(() => {
const savedCart = localStorage.getItem('newswave-cart')
if (savedCart) {
// Restore saved cart items
JSON.parse(savedCart).items.forEach(item => {
dispatch({ type: 'ADD_ITEM', product: item })
})
}
}, [])
// Save cart to localStorage whenever it changes
useEffect(() => {
localStorage.setItem('newswave-cart', JSON.stringify(cart))
}, [cart])
// Helper functions for components to use
const addToCart = (product) => {
dispatch({ type: 'ADD_ITEM', product })
}
const removeFromCart = (productId) => {
dispatch({ type: 'REMOVE_ITEM', productId })
}
return (
<CartContext.Provider value={{ cart, addToCart, removeFromCart }}>
{children}
</CartContext.Provider>
)
}
// Custom hook for easy cart access
export const useCart = () => useContext(CartContext)// components/AddToCart.js - Interactive cart button
'use client'
import { useState } from 'react'
import { useCart } from './CartProvider'
export default function AddToCart({ product }) {
const { addToCart } = useCart()
const [isAdding, setIsAdding] = useState(false)
const handleAddToCart = async () => {
setIsAdding(true) // Show loading state
// Simulate API call delay
await new Promise(resolve => setTimeout(resolve, 500))
addToCart(product) // Add item to cart
setIsAdding(false)
}
return (
<button
onClick={handleAddToCart}
disabled={isAdding || !product.inStock}
className={`px-6 py-3 rounded-lg font-semibold ${
isAdding
? 'bg-gray-400 cursor-not-allowed'
: 'bg-blue-600 hover:bg-blue-700'
} text-white`}
>
{isAdding ? 'Adding...' : `Add to Cart - $${(product.price / 100).toFixed(2)}`}
</button>
)
}We implemented full shopping cart functionality with React Context for state management, local storage for persistence, and interactive quantity controls. Users can add items, adjust quantities, remove products, and see real-time total calculations. Try this: Add items to see the cart populate instantly.
Checkout Process
The checkout process represents your ecommerce funnel's final and most critical stage. Users who reach checkout have strong purchase intent, but poor form design or confusing flows can destroy conversions instantly. Every element needs careful optimization for trust and simplicity. Checkout forms require extensive validation and error handling. Users make typos in email addresses, enter invalid credit card numbers, and forget required fields. Your form needs to catch these issues gracefully while maintaining progress toward completion. Modern checkout experiences minimize form fields and cognitive load. Guest checkout options reduce friction for first-time buyers. Progress indicators show users how many steps remain. Auto-complete and smart defaults speed data entry.// app/checkout/page.js - Streamlined checkout form
'use client'
import { useState } from 'react'
import { useCart } from '@/components/CartProvider'
export default function CheckoutPage() {
const { cart } = useCart()
const [formData, setFormData] = useState({
email: '',
firstName: '',
lastName: '',
address: '',
city: '',
postalCode: ''
})
const [errors, setErrors] = useState({})
// Calculate order total from cart items
const total = cart.items.reduce((sum, item) => {
return sum + (item.price * item.quantity)
}, 0)
const validateForm = () => {
const newErrors = {}
// Check required fields
if (!formData.email) newErrors.email = 'Email is required'
if (!formData.firstName) newErrors.firstName = 'First name is required'
if (!formData.address) newErrors.address = 'Address is required'
// Validate email format
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
if (formData.email && !emailRegex.test(formData.email)) {
newErrors.email = 'Please enter a valid email'
}
setErrors(newErrors)
return Object.keys(newErrors).length === 0
}// Handle checkout form submission
const handleSubmit = async (e) => {
e.preventDefault()
if (!validateForm()) {
return // Don't submit if validation fails
}
try {
// In real app, this would call payment processor
const response = await fetch('/api/checkout', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
items: cart.items,
customer: formData,
total: total
})
})
if (response.ok) {
// Redirect to success page with order details
window.location.href = '/checkout/success'
} else {
throw new Error('Payment failed')
}
} catch (error) {
setErrors({ general: 'Something went wrong. Please try again.' })
}
}We built a complete checkout experience with form validation, order summary, and secure submission handling. The layout separates shipping information from order details for clarity. Real-time validation prevents common errors before submission.
Production Considerations
Real ecommerce applications require additional infrastructure beyond the core shopping features. Payment processing, inventory management, order fulfillment, and customer support systems transform your prototype into a business-ready platform. Payment integration with services like Stripe or PayPal handles sensitive financial data securely. These providers manage PCI compliance, fraud detection, and international payment methods. Your application focuses on user experience while delegating security concerns to specialists. Inventory management becomes critical as your catalog grows. Out-of-stock items need clear indicators. Popular products require automatic restocking alerts. Seasonal merchandise needs scheduling capabilities. Database-driven inventory systems replace static product arrays. Performance optimization scales your store for high traffic. Image optimization reduces page load times. CDN distribution serves global customers quickly. Caching strategies minimize database queries. Server-side rendering improves SEO rankings for product pages.Payment processing integration, inventory database with real-time updates, order management dashboard, email notification system, mobile-responsive design testing, security auditing, performance monitoring, backup systems, customer support tools, analytics tracking, and legal compliance (privacy policy, terms of service).
Quiz
1. Why does the NewsWave store use static generation for product pages instead of server-side rendering?
2. What makes React Context ideal for shopping cart state management in the NewsWave store?
3. How does Next.js handle individual product pages like /shop/newswave-hoodie in the store?