How to Build a Production-Ready eCommerce Website with React, Tailwind CSS, PlanetScale and Stripe

In this expert guide, we‘ll walk through building a complete, production-ready ecommerce website from the ground up using some of the most powerful and developer-friendly tools available today:

  • React for creating dynamic, component-based UIs
  • Tailwind CSS for styling with a utility-first approach
  • PlanetScale for a scalable serverless MySQL database
  • Stripe for secure payment processing

By the end, you‘ll have a fully-functional ecommerce site ready to launch and grow your online business. Let‘s dive in!

Project Setup

First, make sure you have Node.js installed. Open your terminal and navigate to the directory where you want to create your project. Run the following commands:

npx create-next-app@latest ecommerce-site
cd ecommerce-site
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

This creates a new Next.js app (which uses React), installs Tailwind CSS, and creates the necessary config files.

Replace the contents of tailwind.config.js with:

module.exports = {
  content: [
    "./pages/**/*.{js,ts,jsx,tsx}",
    "./components/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

And add the Tailwind directives to styles/globals.css:

@tailwind base;
@tailwind components;
@tailwind utilities;

Run npm run dev and open http://localhost:3000 to see the default Next.js starter.

Creating Components and Pages

Let‘s start building the site! We‘ll use React components to create reusable UI elements and Next.js pages for each main view.

The ecommerce site will have the following main pages:

  • Home page with featured products
  • Product catalog
  • Individual product details
  • Shopping cart
  • Checkout
  • Order confirmation

Plus some supplementary pages like About, Contact, FAQ, etc.

Each page will be made up of components like the Header, Footer, ProductCard, CartItem, etc. Components allow us to break the UI into independent, reusable pieces.

Here‘s an example of a simple Header component using Tailwind CSS classes:

const Header = () => (
  <header className="sticky top-0 bg-white shadow-md z-50">
    <div className="max-w-6xl mx-auto px-4">
      <div className="flex justify-between items-center py-4">
        <div className="flex items-center">
          <img src="/logo.svg" alt="Logo" className="h-8 w-auto"/>
          <span className="ml-2 text-xl font-semibold">My Store</span>
        </div>
        <nav>
          <ul className="flex space-x-4">
            <li>
              <Link href="/">
                <a className="text-gray-600 hover:text-blue-600">Home</a>
              </Link>
            </li>
            <li>
              <Link href="/products">
                <a className="text-gray-600 hover:text-blue-600">Products</a>
              </Link>  
            </li>
            <li>
              <Link href="/cart">
                <a className="text-gray-600 hover:text-blue-600">Cart</a>
              </Link>
            </li>
          </ul>
        </nav>
      </div>
    </div>
  </header>
)

This uses Tailwind‘s pre-defined utility classes to style the header, handle layout, and add hover effects. No custom CSS needed!

Other key components will include:

  • ProductCard to display a product image, name, price, and "Add to Cart" button
  • CartItem to display a product in the cart with quantity controls
  • CheckoutForm to capture shipping and payment details

By composing an entire UI from smaller components, the codebase stays organized and maintainable as it grows.

Integrating with PlanetScale

For the backend database to power our ecommerce site, we‘ll use PlanetScale – a serverless MySQL platform that automatically scales, supports branching workflows, and integrates easily with Prisma ORM.

First, sign up for a free PlanetScale account and create a new database. Install the PlanetScale CLI and Prisma:

npm install -g planetscale prisma

Run pscale auth login to connect the CLI to your PlanetScale account. Then create a new branch to develop our database schema:

pscale branch create ecommerce-site init

In your project directory, run prisma init to set up Prisma. Open the generated schema.prisma file and add:

datasource db {
  provider = "mysql"
  url = env("DATABASE_URL")
  relationMode = "prisma"
}

model Product {
  id          Int     @id @default(autoincrement())
  name        String
  description String
  price       Int
  image       String
  createdAt   DateTime @default(now())
  updatedAt   DateTime @updatedAt
}

model Order {
  id        Int      @id @default(autoincrement())
  items     Json
  total     Int
  status    String
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt  
}

This defines the Product and Order models that will be stored in the database, with fields like name, price, creation date, order status, etc.

To get the DATABASE_URL for the Prisma datasource:

pscale connect ecommerce-site init --format prisma

Copy the output and add it to a new .env file. Then run:

prisma generate
prisma db push

To generate the Prisma client and push the schema to the PlanetScale database.

Now we can use Prisma in our API routes to query and update data. For example, here‘s an API route to get a list of products:

import prisma from ‘lib/prisma‘

export default async function handler(req, res) {
  if (req.method === ‘GET‘) {
    const products = await prisma.product.findMany()
    res.json(products)
  }
}

And one to create a new order:

import prisma from ‘lib/prisma‘

export default async function handler(req, res) {
  if (req.method === ‘POST‘) {
    const { items, total } = req.body

    const order = await prisma.order.create({
      data: {
        items,
        total, 
        status: ‘pending‘
      }
    })

    res.json(order)
  }
}

On the frontend, we can use fetch or a library like SWR to query the API routes and display the returned data.

PlanetScale and Prisma make it incredibly easy to develop and manage a production-grade database. Branching allows us to prototype schema changes safely, while PlanetScale‘s autoscaling means the database will perform reliably under load.

Processing Payments with Stripe

To securely accept payments, we‘ll integrate Stripe Checkout into the frontend and backend of our ecommerce site.

First, sign up for a Stripe account and get your API keys. Install the Stripe Node library:

npm install stripe

And add your Stripe secret key to .env:

STRIPE_SECRET_KEY=sk_test_...

To initiate the checkout process when a customer clicks the "Checkout" button, we‘ll make a request to a new API route that creates a Stripe Checkout session:

import Stripe from ‘stripe‘;

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

export default async function handler(req, res) {
  if (req.method === ‘POST‘) {
    try {
      const { items } = req.body;

      const session = await stripe.checkout.sessions.create({
        payment_method_types: [‘card‘],
        line_items: items.map(item => ({
          price_data: {
            currency: ‘usd‘,
            product_data: {
              name: item.name,
              images: [item.image],
            },
            unit_amount: item.price * 100,
          },
          quantity: item.qty,
        })),
        mode: ‘payment‘,
        success_url: `${req.headers.origin}/success`,
        cancel_url: `${req.headers.origin}/cart`,
      });

      res.json({ id: session.id });
    } catch (err) {
      res.status(500).json({ error: err.message })
    }
  }
}

This creates a new Checkout session with the provided line items and returns the session ID to the frontend.

On the frontend, we initialize Stripe and redirect to the returned Checkout session URL:

import { loadStripe } from ‘@stripe/stripe-js‘;

const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY);

const CheckoutButton = ({ items }) => {
  const handleCheckout = async () => {
    const stripe = await stripePromise;

    const res = await fetch(‘/api/checkout‘, {
      method: ‘POST‘,
      headers: { ‘Content-Type‘: ‘application/json‘ },
      body: JSON.stringify({ items }),
    });
    const { id } = await res.json();

    await stripe.redirectToCheckout({ sessionId: id });
  }

  return (
    <button 
      className="bg-blue-500 text-white px-4 py-2 rounded"
      onClick={handleCheckout}
    >
      Checkout
    </button>
  )
}

When the customer completes the checkout, Stripe will redirect back to our success_url. Here we can show an order confirmation and save the order details to our database.

With Stripe Checkout, you can easily accept payments while letting Stripe handle all the complexities of PCI compliance, fraud protection, and more.

Optimizing for Production

To ensure our ecommerce site performs well and provides an excellent user experience, there are a few key things to optimize:

  • Page load speed – Use next/image for optimized images, minimize and cache assets, lazy load below-the-fold content. Aim for a Lighthouse performance score of 90+.
  • Responsive design – Use Tailwind‘s responsive utility classes to ensure the site looks great and functions well on any device size. Test thoroughly on real devices.
  • Accessibility – Follow WAI-ARIA guidelines, use semantic HTML, provide text alternatives for media, ensure the site is keyboard navigable. Aim for a Lighthouse accessibility score of 100.
  • SEO – Add optimized title tags and meta descriptions to each page, use structured data where relevant, generate an XML sitemap, consider using next/head to set per-page metadata.
  • Security – Keep dependencies up to date, use HTTPS, secure authentication, sanitize user input, limit attack surface. Consider security scanning tools and pentesting before launch.

Before launching, thoroughly test the entire checkout flow, test with different user roles (customer, admin, etc.) and do some load testing to ensure the site remains performant under heavy traffic.

Deployment

Finally, it‘s time to deploy our production-ready ecommerce site and share it with the world!

Deploying a Next.js app is easy with Vercel. Sign up for a free account, connect your Git repository, and Vercel will automatically build and deploy the app with each new commit.

For the backend database, promote your PlanetScale init branch to the main production branch:

pscale deploy-request create ecommerce-site init

Review and approve the deploy request in the PlanetScale web UI. Your database schema will be deployed to production and ready to serve live traffic.

Wrap-up

And there you have it! A complete, production-ready ecommerce website powered by some of the best tools in web development.

Of course, this is just the beginning. You can further extend your site with features like:

  • User accounts and order history
  • Product search and filtering
  • Related product recommendations
  • Discount codes and promotions
  • Wishlists and saved carts
  • Product reviews and ratings
  • Inventory management
  • Abandoned cart recovery
  • Multi-language and multi-currency

By using React, Tailwind CSS, PlanetScale, Prisma, Next.js, and Stripe, you have a solid foundation to grow your ecommerce business while delivering a first-class customer experience. The possibilities are endless!

Similar Posts