Building E-Commerce Platforms with Next.js

October 12, 2024 (1y ago)

Building E-Commerce Platforms with Next.js

E-commerce demands high performance, reliability, and excellent UX. Building Floral Radiance taught me valuable lessons about creating production-ready online stores.

Architecture Overview

Technology Stack

Core Features Implementation

Product Catalog System

// lib/products.ts
interface Product {
  id: number;
  title: string;
  description: string;
  price: number;
  images: string[];
  stock: number;
  category: string;
  createdAt: Date;
}
 
export async function getProducts(
  category?: string,
  sortBy?: "price" | "newest",
): Promise<Product[]> {
  const query = new URLSearchParams();
  if (category) query.append("category", category);
  if (sortBy) query.append("sort", sortBy);
 
  const response = await fetch(`/api/products?${query}`);
  return response.json();
}

Shopping Cart Management

// context/CartContext.tsx
import { createContext, useContext, useState } from "react";
 
interface CartItem {
  productId: number;
  quantity: number;
  price: number;
}
 
interface CartContextType {
  items: CartItem[];
  addItem: (item: CartItem) => void;
  removeItem: (productId: number) => void;
  updateQuantity: (productId: number, quantity: number) => void;
  total: number;
}
 
const CartContext = createContext<CartContextType | null>(null);
 
export function CartProvider({ children }: { children: React.ReactNode }) {
  const [items, setItems] = useState<CartItem[]>([]);
 
  const total = items.reduce((sum, item) => sum + item.price * item.quantity, 0);
 
  return (
    <CartContext.Provider
      value={{
        items,
        addItem: (item) => setItems([...items, item]),
        removeItem: (id) => setItems(items.filter((i) => i.productId !== id)),
        updateQuantity: (id, qty) =>
          setItems(
            items.map((i) => (i.productId === id ? { ...i, quantity: qty } : i))
          ),
        total,
      }}
    >
      {children}
    </CartContext.Provider>
  );
}
 
export const useCart = () => {
  const context = useContext(CartContext);
  if (!context) throw new Error("useCart must be used within CartProvider");
  return context;
};

Payment Integration

// pages/api/checkout.ts
import Stripe from "stripe";
 
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
 
export default async function handler(req, res) {
  if (req.method !== "POST") return res.status(405).end();
 
  const { items } = req.body;
 
  const session = await stripe.checkout.sessions.create({
    payment_method_types: ["card"],
    line_items: items.map((item: CartItem) => ({
      price_data: {
        currency: "usd",
        product_data: {
          name: item.productName,
        },
        unit_amount: Math.round(item.price * 100),
      },
      quantity: item.quantity,
    })),
    mode: "payment",
    success_url: `${process.env.NEXTAUTH_URL}/success`,
    cancel_url: `${process.env.NEXTAUTH_URL}/cart`,
  });
 
  res.json({ sessionId: session.id });
}

Database Schema for E-Commerce

// schema.prisma
model Product {
  id          Int     @id @default(autoincrement())
  title       String
  description String
  price       Decimal
  stock       Int
  images      String[]
  category    Category @relation(fields: [categoryId], references: [id])
  categoryId  Int
  orders      Order[]
  createdAt   DateTime @default(now())
  updatedAt   DateTime @updatedAt
}
 
model Order {
  id        Int      @id @default(autoincrement())
  user      User     @relation(fields: [userId], references: [id])
  userId    Int
  products  Product[]
  total     Decimal
  status    String
  createdAt DateTime @default(now())
}
 
model Category {
  id       Int       @id @default(autoincrement())
  name     String
  products Product[]
}

Performance Optimization for E-Commerce

Image Optimization

// components/ProductImage.tsx
import Image from "next/image";
 
export function ProductImage({ src, alt }: { src: string; alt: string }) {
  return (
    <Image
      src={src}
      alt={alt}
      width={500}
      height={500}
      placeholder="blur"
      blurDataURL="data:image/jpeg,..."
      quality={85}
    />
  );
}

Search and Filtering

// pages/api/products/search.ts
export default async function handler(req, res) {
  const { q, minPrice, maxPrice, category } = req.query;
 
  const products = await prisma.product.findMany({
    where: {
      AND: [
        q ? { title: { contains: q as string, mode: "insensitive" } } : {},
        minPrice ? { price: { gte: Number(minPrice) } } : {},
        maxPrice ? { price: { lte: Number(maxPrice) } } : {},
        category ? { category: { name: category as string } } : {},
      ],
    },
  });
 
  res.json(products);
}

Admin Dashboard Features

Key features for store management:

Real-World Example: Floral Radiance

Lessons from building Floral Radiance:

Best Practices for E-Commerce

Practice Implementation
Data Validation Server-side validation for all inputs
Security PCI compliance, secure payment handling
Performance CDN for assets, caching strategies
UX Clear checkout flow, guest checkout option
Analytics Track conversion funnels, user behavior

Conclusion

Building e-commerce platforms requires balancing functionality, performance, security, and user experience. Next.js combined with modern backend services makes it possible to create world-class online stores.