Skip to main content
Ecommerce with Next.js
Ecommerce14 min read

How to Build a Custom Ecommerce Store with Next.js

Zeeshan Waheed
Zeeshan Waheed

July 1, 2026

Share

Part of a Series

Ecommerce Development: Build Your Online Store

This article is part of the Ecommerce Development content cluster. Explore the complete guide for in-depth coverage of this topic.

View complete guide →

Building a custom ecommerce store with Next.js gives you complete control over your shopping experience without the limitations of traditional platforms like Shopify or WooCommerce. This guide walks you through everything you need to build a production-ready online store from scratch.

Whether you are a developer looking to launch a side project or a business owner wanting a fully custom solution, this guide covers the complete stack: frontend, backend, payments, orders, and deployment. By the end, you'll have a fast, secure, and scalable ecommerce store tailored exactly to your needs.

If you'd rather have a team build it for you, check out my ecommerce store development services or custom web development offerings.

Why Next.js for Ecommerce?

Next.js has become the go-to framework for ecommerce for three reasons: performance, SEO, and flexibility. Unlike Shopify or WooCommerce, Next.js gives you complete control over every pixel of your store while delivering lightning-fast page loads.

Performance

Next.js supports Static Site Generation (SSG), Server-Side Rendering (SSR), and Incremental Static Regeneration (ISR). Product pages can be pre-built at deploy time and served from a CDN. Pages load instantly and Core Web Vitals scores stay consistently high. Compare this to Shopify, where you are limited to their Liquid templating engine and shared hosting infrastructure, or WooCommerce, which requires PHP execution on every request.

SEO

Search engines see fully-rendered HTML from the server, not a blank JavaScript shell. You control every meta tag, every structured data snippet, and every canonical URL. Product pages rank better with Next.js because they load faster and render complete HTML on first byte.

Flexibility

Shopify locks you into their ecosystem. WooCommerce ties you to WordPress and PHP. Next.js lets you use any headless CMS, any payment gateway, any search service, and any hosting provider. You are not paying recurring platform fees or dealing with plugin incompatibilities.

For a deeper comparison, read my article on Next.js vs WordPress and the complete guide to building a Next.js website.

Architecture Overview

A custom Next.js ecommerce store follows a headless commerce architecture. The frontend (Next.js) is completely decoupled from the backend services. Here is how the pieces fit together:

  • Next.js Frontend — Handles all UI rendering, routing, and client-side state for the shopping cart. Deployed to Vercel or a CDN.
  • Stripe — Processes payments. Stripe Checkout handles PCI compliance so you never touch raw card data.
  • Headless CMS — Manages product information, categories, and content. Connected via REST or GraphQL API.
  • Database — Stores orders, customers, and inventory. PostgreSQL or MongoDB behind a secure API.
  • Search Service — Algolia or Meilisearch for fast product search and faceted filtering.

The beauty of this separation: your frontend can scale independently, your payment data never touches your server, and switching any backend service does not require rebuilding the storefront.

Tech Stack Recommendations

Based on production experience, here is the recommended stack for a custom Next.js ecommerce store:

  • Framework: Next.js 15 (App Router, Server Components)
  • Styling: Tailwind CSS + shadcn/ui for components
  • Payments: Stripe (primary) + PayPal (secondary)
  • CMS: Sanity or Strapi for product/content management
  • Database: PostgreSQL + Prisma ORM
  • Search: Algolia or Meilisearch
  • Hosting: Vercel (frontend) + Railway or AWS (backend)
  • Authentication: NextAuth.js or Clerk
  • Email: Resend or SendGrid for order confirmations

For Pakistani merchants, add JazzCash and Easypaisa API integration to capture the growing local payment market.

Step-by-Step Implementation

1. Project Setup

Create your Next.js project with the App Router: npx create-next-app@latest ecommerce-store --typescript --tailwind --app. Install essential dependencies: npm install @stripe/stripe-js @stripe/react-stripe-js prisma @prisma/client next-auth lucide-react.

2. Product Catalog with Dynamic Routes

Create a app/products/[slug]/page.tsx dynamic route. Fetch product data from your CMS or database using server components. Generate static params for all products using generateStaticParams for instant page loads.

3. Shopping Cart (Client-Side State)

Use React Context or Zustand to manage the cart state on the client. Store cart items in localStorage for persistence across sessions. Implement add, remove, update quantity, and clear operations.

4. Checkout with Stripe

Redirect users to Stripe Checkout for secure payment processing. Create a Checkout Session on the server via an API route, passing line items, customer email, and success/cancel URLs. Stripe handles the rest.

5. Order Management

When Stripe confirms payment, a webhook hits your server. Use this to create an order in your database, send a confirmation email, and update inventory. Store order status, customer details, and payment metadata.

6. Admin Dashboard

Build a protected admin area with Next.js API routes. Show order history, update order statuses, manage products, and view analytics. Use NextAuth.js for authentication and role-based access.

Code Examples

Product Page (app/products/[slug]/page.tsx)

import { notFound } from "next/navigation";
import { prisma } from "@/lib/prisma";
import { AddToCartButton } from "@/components/AddToCartButton";
import Image from "next/image";

export async function generateStaticParams() {
  const products = await prisma.product.findMany();
  return products.map((p) => ({ slug: p.slug }));
}

export default async function ProductPage({
  params,
}: {
  params: Promise<{ slug: string }>;
}) {
  const { slug } = await params;
  const product = await prisma.product.findUnique({ where: { slug } });
  if (!product) notFound();

  return (
    <div className="grid md:grid-cols-2 gap-8 p-8">
      <Image
        src={product.image}
        alt={product.name}
        width={600}
        height={600}
        className="rounded-lg"
      />
      <div>
        <h1 className="text-3xl font-bold">{product.name}</h1>
        <p className="text-2xl font-semibold mt-2">
          ${(product.price / 100).toFixed(2)}
        </p>
        <p className="mt-4 text-foreground/70">{product.description}</p>
        <AddToCartButton product={product} />
      </div>
    </div>
  );
}

Shopping Cart Context

"use client";
import { createContext, useContext, useReducer, useEffect } from "react";

type CartItem = {
  id: string;
  name: string;
  price: number;
  quantity: number;
  image: string;
};

type CartAction =
  | { type: "ADD"; item: CartItem }
  | { type: "REMOVE"; id: string }
  | { type: "UPDATE_QTY"; id: string; quantity: number };

function cartReducer(state: CartItem[], action: CartAction): CartItem[] {
  switch (action.type) {
    case "ADD": {
      const existing = state.find((i) => i.id === action.item.id);
      if (existing) {
        return state.map((i) =>
          i.id === action.item.id
            ? { ...i, quantity: i.quantity + 1 }
            : i
        );
      }
      return [...state, { ...action.item, quantity: 1 }];
    }
    case "REMOVE":
      return state.filter((i) => i.id !== action.id);
    case "UPDATE_QTY":
      return state.map((i) =>
        i.id === action.id ? { ...i, quantity: action.quantity } : i
      );
    default:
      return state;
  }
}

const CartContext = createContext<{
  items: CartItem[];
  dispatch: React.Dispatch<CartAction>;
  total: number;
}>(null!);

export function CartProvider({ children }: { children: React.ReactNode }) {
  const [items, dispatch] = useReducer(cartReducer, [], () => {
    if (typeof window !== "undefined") {
      const stored = localStorage.getItem("cart");
      return stored ? JSON.parse(stored) : [];
    }
    return [];
  });

  useEffect(() => {
    localStorage.setItem("cart", JSON.stringify(items));
  }, [items]);

  const total = items.reduce((sum, i) => sum + i.price * i.quantity, 0);

  return (
    <CartContext.Provider value={{ items, dispatch, total }}>
      {children}
    </CartContext.Provider>
  );
}

export const useCart = () => useContext(CartContext);

Stripe Checkout API Route (app/api/checkout/route.ts)

import { NextResponse } from "next/server";
import Stripe from "stripe";

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);

export async function POST(req: Request) {
  const { items, customerEmail } = await req.json();

  const session = await stripe.checkout.sessions.create({
    payment_method_types: ["card"],
    mode: "payment",
    customer_email: customerEmail,
    line_items: items.map((item: any) => ({
      price_data: {
        currency: "usd",
        product_data: {
          name: item.name,
          images: [item.image],
        },
        unit_amount: item.price,
      },
      quantity: item.quantity,
    })),
    success_url: `${process.env.NEXT_PUBLIC_URL}/order/success?session_id={CHECKOUT_SESSION_ID}`,
    cancel_url: `${process.env.NEXT_PUBLIC_URL}/cart`,
  });

  return NextResponse.json({ url: session.url });
}

Stripe Webhook for Order Processing

import { NextResponse } from "next/server";
import Stripe from "stripe";
import { prisma } from "@/lib/prisma";

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET!;

export async function POST(req: Request) {
  const body = await req.text();
  const sig = req.headers.get("stripe-signature")!;

  let event: Stripe.Event;
  try {
    event = stripe.webhooks.constructEvent(body, sig, webhookSecret);
  } catch {
    return NextResponse.json(
      { error: "Invalid signature" },
      { status: 400 }
    );
  }

  if (event.type === "checkout.session.completed") {
    const session = event.data.object as Stripe.Checkout.Session;

    await prisma.order.create({
      data: {
        stripeSessionId: session.id,
        customerEmail: session.customer_email!,
        amountTotal: session.amount_total!,
        status: "paid",
        items: {
          create: session.line_items?.data?.map((item) => ({
            name: item.description!,
            quantity: item.quantity!,
            price: item.amount_total!,
          })) ?? [],
        },
      },
    });
  }

  return NextResponse.json({ received: true });
}

Performance Optimization

Performance is critical for ecommerce. A one-second delay can reduce conversions by 20%. Here is how to keep your Next.js store blazing fast:

Incremental Static Regeneration (ISR)

Use ISR for product pages so they are served statically but update when inventory or pricing changes: export const revalidate = 300; revalidates every 5 minutes. For instant updates on price changes, use on-demand revalidation via the Stripe webhook.

Image Optimization

Use Next.js next/image for automatic WebP/AVIF conversion, responsive srcset, lazy loading, and blur placeholders. Always set explicit dimensions to prevent layout shift.

CDN Delivery

Deploy to Vercel (which uses a global CDN by default) or use Cloudflare as a reverse proxy. Static pages are served from edge locations closest to the user, reducing latency to under 50ms globally.

Code Splitting

Use dynamic imports for heavy components like the cart drawer, checkout form, and admin dashboard. The initial page load should only ship critical JavaScript.

Font and CSS Optimization

Use next/font to load fonts with zero layout shift. Tailwind CSS purges unused styles in production automatically. Inline critical CSS for above-the-fold content.

SEO for Ecommerce

SEO drives organic traffic to your store. Here are the essential SEO techniques for a Next.js ecommerce site:

Product Schema (JSON-LD)

Add structured data to every product page so Google displays rich results with price, availability, and reviews. Use the Product schema type:

{
  "@context": "https://schema.org",
  "@type": "Product",
  "name": "Product Name",
  "image": "https://example.com/product.jpg",
  "description": "Product description",
  "sku": "SKU123",
  "offers": {
    "@type": "Offer",
    "priceCurrency": "USD",
    "price": "49.99",
    "availability": "https://schema.org/InStock"
  }
}

Category Pages

Create optimized category pages with unique meta titles, descriptions, and breadcrumb structured data. Use generateMetadata in Next.js to set dynamic titles and descriptions based on the category.

Canonical URLs

Prevent duplicate content issues by setting canonical URLs on every page. This is especially important for product pages that might be accessible through multiple URL paths (e.g., /products/shoe and /category/footwear/shoe).

Sitemaps and Robots

Generate dynamic XML sitemaps that include all product and category URLs. Submit to Google Search Console. Use a robots.txt that allows crawling of product pages but blocks cart, checkout, and admin routes.

Security Considerations

Ecommerce security is not optional. Here is what you need to protect your customers and your business:

PCI Compliance

Using Stripe Checkout means you never handle raw credit card data. Stripe is PCI DSS Level 1 compliant. Your app only receives tokenized payment information. This dramatically reduces your PCI compliance scope to SAQ A (the simplest self-assessment questionnaire).

Payment Data Handling

Never log payment details, never store card numbers, never send payment data through your own API. Use Stripe Elements or Stripe Checkout to keep sensitive data out of your server logs and database.

CSRF Protection

Next.js Server Actions include built-in CSRF protection. For API routes, use SameSite cookies and CSRF tokens. Validate the origin header on all incoming requests to prevent cross-site request forgery attacks.

Additional Security Measures

  • Use Helmet middleware for HTTP security headers
  • Rate-limit API routes to prevent abuse
  • Validate and sanitize all user input server-side
  • Use environment variables for all secrets
  • Enable 2FA on your Stripe dashboard and hosting provider
  • Regular dependency audits with npm audit

Payment Gateway Integration

Stripe

Stripe is the gold standard for Next.js ecommerce. The Stripe JS library integrates seamlessly with Next.js. Use Stripe Checkout for hosted payments or Stripe Elements for a custom checkout flow. Features: subscriptions, multi-currency, Apple Pay, Google Pay, and instant bank transfers.

PayPal

Add PayPal as a secondary option using the PayPal JS SDK or PayPal Commerce Platform. Many customers prefer PayPal for its buyer protection. Integrate it alongside Stripe by showing both payment options at checkout.

Local Payment Gateways (Pakistan — JazzCash & Easypaisa)

For stores targeting the Pakistani market, integrating local payment methods is essential. Over 70% of online transactions in Pakistan use JazzCash or Easypaisa. Here is how to integrate them:

  • JazzCash API: Use the JazzCash Merchant API for direct payment processing. Requires a merchant account with Mobilink Microfinance Bank. The API supports mobile account and wallet payments.
  • Easypaisa API: Telenor Microfinance Bank provides the Easypaisa merchant API. Similar to JazzCash, it enables mobile account transfers and wallet payments.
  • Third-Party Aggregators: Services like Payfast, SadaPay, and NayaPay offer unified APIs that combine Stripe-like functionality with local payment support.

Implementation typically involves creating an API route that generates a payment request to the gateway, handling the callback URL for payment confirmation, and verifying the transaction server-side before updating the order status.

Common Mistakes

  • Skipping ISR for product pages — Without ISR, every product page load hits your server. With ISR, pages are served from the CDN and revalidated in the background.
  • Storing cart in the database — Client-side cart state with localStorage is faster and simpler. Only persist to the database when the user checks out or logs in.
  • Not validating webhook signatures — Anyone can POST to your webhook endpoint. Always verify the Stripe signature before processing the event.
  • Ignoring mobile checkout UX — Over 70% of ecommerce traffic is mobile. Test your checkout flow on real mobile devices, not just the DevTools emulator.
  • Hard-coding currency and locale — Support multiple currencies from day one using Stripe's currency parameter. Store the user's locale and format prices accordingly.
  • Skipping error tracking — Payment failures, webhook errors, and checkout abandons happen silently. Use Sentry or Logtail to capture and alert on errors in production.

Frequently Asked Questions

Is Next.js suitable for large ecommerce stores with thousands of products?

Absolutely. Next.js handles thousands of product pages efficiently through Static Site Generation and ISR. Use generateStaticParams to pre-build popular products and ISR for the rest. Algolia or Meilisearch handles product search independently of the page count.

Do I need a backend server for a Next.js ecommerce store?

Not necessarily. Next.js API routes serve as your backend. Combined with Prisma for the database, Stripe for payments, and a headless CMS for content, you can build a complete store without a separate backend server. For complex requirements, add a dedicated backend.

How do I handle inventory management with Next.js and Stripe?

Track inventory in your database using Prisma. Decrement stock when an order is confirmed via Stripe webhook. For real-time accuracy, use Stripe's inventory management features or integrate with a third-party inventory service like TradeGecko.

Can I use WooCommerce products with a Next.js frontend?

Yes. WooCommerce has a REST API and GraphQL plugin that expose products, categories, and orders. Use Next.js as a headless frontend that fetches data from WooCommerce. This gives you WordPress's admin panel with Next.js's performance.

What's the hosting cost for a Next.js ecommerce store?

Vercel's Pro plan starts at $20/month. Database hosting (Railway or Supabase) is $10-25/month. Stripe charges 2.9% + $0.30 per transaction. Total base cost is $30-45/month before payment processing fees, significantly less than Shopify's $39/month basic plan with no transaction fees when using Stripe.

How do I add multi-currency support to my Next.js store?

Stripe supports 135+ currencies natively. Pass the currency parameter to the Checkout Session. Use the user's IP geolocation or browser locale to detect their preferred currency. Store prices in your base currency and convert using a real-time exchange rate API.

Is Next.js good for Pakistani ecommerce stores with JazzCash/Easypaisa?

Yes. Next.js with a headless architecture is ideal for Pakistani ecommerce. You can integrate JazzCash and Easypaisa APIs directly via Next.js API routes. The static generation ensures fast page loads even on slower connections common in Pakistan. CDN delivery via Vercel reduces latency for local users.

Summary & Next Steps

Building a custom ecommerce store with Next.js gives you performance, flexibility, and control that traditional platforms cannot match. You own your code, your data, and your customer relationships without paying recurring platform fees or fighting template limitations.

Start by setting up your Next.js project with Stripe integration. Build your product catalog with dynamic routes. Implement the cart with client-side state management. Process payments securely through Stripe Checkout. Add order management via webhooks. Polish with ISR, SEO, and local payment gateways for your target market.

This is a significant project, but the result is a store that loads instantly, ranks well in search, and gives you complete control over the shopping experience. If you need expert help, I offer professional ecommerce development services and custom web development tailored to your business needs.

For more reading, check out Next.js vs WordPress for ecommerce and the complete Next.js website guide. Contact me to discuss your project or request a quote to get started today.

Start Your Ecommerce Project

Build an online store that converts visitors into customers.

About the Author

Zeeshan Waheed

Zeeshan Waheed

Senior Full Stack Engineer & Web Security Expert with 8+ years of experience. Specializing in Next.js, React, Node.js, cybersecurity, and AI integration.

8+ Years Experience300+ ProjectsFull StackSecurity Expert
Zeeshan Waheed
Zeeshan Waheed·

Get the Latest Insights

Subscribe to receive new articles, tutorials, and updates directly in your inbox.

your@email.com
Subscribe