Build a Simple E-commerce Product Listing with Next.js

In the ever-evolving landscape of web development, creating dynamic and user-friendly web applications is paramount. E-commerce, in particular, demands websites that are not only visually appealing but also efficient and performant. Next.js, a React framework known for its server-side rendering (SSR) and static site generation (SSG) capabilities, has become a go-to solution for building such applications. This article will guide you through building a simple product listing page using Next.js, equipping you with the fundamental skills to create more complex e-commerce features.

Why Next.js for E-commerce?

Before diving into the code, let’s understand why Next.js is a great choice for e-commerce projects:

  • Performance: SSR and SSG significantly improve initial load times and SEO. Search engines love fast websites!
  • SEO-Friendly: SSR ensures that search engine crawlers can easily index your content, improving your website’s visibility.
  • Developer Experience: Next.js offers features like hot-reloading, built-in routing, and easy deployment, making development smoother.
  • Scalability: Next.js is designed to handle large-scale applications, making it suitable for growing e-commerce businesses.

Project Setup

Let’s get started by setting up our Next.js project. Open your terminal and run the following command:

npx create-next-app product-listing-app --typescript

This command creates a new Next.js project named “product-listing-app” with TypeScript support. Navigate into your project directory:

cd product-listing-app

Now, install any necessary dependencies. For this project, we will use a hypothetical product data source (you can replace this with your actual data source later). We won’t be using any external libraries for now, to keep things simple and focus on the core concepts of Next.js. However, if you’d like to use a CSS framework like Tailwind CSS, you can install it using npm or yarn.

npm install

Data Modeling

Before we build the UI, let’s define the structure of our product data. Create a file named `product.ts` in a `types` directory (if you don’t have one, create it in the root directory) and add the following code:

// types/product.ts
export interface Product {
  id: number;
  name: string;
  description: string;
  price: number;
  imageUrl: string;
}

This interface defines the properties of a product: `id`, `name`, `description`, `price`, and `imageUrl`. This is a basic example; your product data might have more fields.

Fetching Product Data

For this tutorial, we will simulate fetching product data. In a real-world scenario, you’d fetch this data from an API, a database, or a content management system (CMS). Create a file called `products.ts` in a `lib` directory (create the directory if it doesn’t exist) and add the following code:


// lib/products.ts
import { Product } from '../types/product';

const products: Product[] = [
  {
    id: 1,
    name: 'Awesome T-Shirt',
    description: 'A comfortable and stylish t-shirt.',
    price: 25,
    imageUrl: '/images/tshirt.jpg',
  },
  {
    id: 2,
    name: 'Cool Cap',
    description: 'Protect yourself from the sun with style.',
    price: 15,
    imageUrl: '/images/cap.jpg',
  },
  {
    id: 3,
    name: 'Trendy Jeans',
    description: 'The perfect pair of jeans for any occasion.',
    price: 75,
    imageUrl: '/images/jeans.jpg',
  },
];

export async function getProducts(): Promise {
  // Simulate an API call with a 1-second delay
  await new Promise((resolve) => setTimeout(resolve, 1000));
  return products;
}

This code defines a `getProducts` function that simulates fetching product data. It returns a promise that resolves after a 1-second delay, mimicking an API call. Replace the sample data with your actual product data or connect to a real API.

Creating the Product Listing Page

Now, let’s create the product listing page. Open the `pages/index.tsx` file (or `pages/index.js` if you are not using TypeScript) and replace its content with the following code:


// pages/index.tsx
import { GetStaticProps } from 'next';
import Image from 'next/image';
import { Product } from '../types/product';
import { getProducts } from '../lib/products';

interface Props {
  products: Product[];
}

const Home: React.FC = ({ products }) => {
  return (
    <div>
      <h1>Our Products</h1>
      <div>
        {products.map((product) => (
          <div>
            
            <h2>{product.name}</h2>
            <p>{product.description}</p>
            <p>${product.price}</p>
          </div>
        ))}
      </div>
    </div>
  );
};

export const getStaticProps: GetStaticProps = async () => {
  const products = await getProducts();
  return {
    props: { products },
    revalidate: 10, // Revalidate the data every 10 seconds
  };
};

export default Home;

Let’s break down this code:

  • Imports: We import `GetStaticProps` from ‘next’, `Image` from ‘next/image’, our `Product` interface, and the `getProducts` function.
  • Props Interface: We define an interface `Props` to specify the type of the `products` prop.
  • Home Component: This is our main component, which receives the `products` prop. It renders a heading and then iterates over the `products` array, displaying each product’s information. I’ve added some basic styling using Tailwind CSS classes for layout and appearance. Feel free to customize this to your liking.
  • getStaticProps: This is a Next.js function that runs at build time. It fetches the product data using `getProducts` and passes it as props to the `Home` component. The `revalidate` option tells Next.js to regenerate the page every 10 seconds. This is helpful for keeping your product listings updated without needing to rebuild the entire site.

Running the Application

To run your application, execute the following command in your terminal:

npm run dev

This command starts the development server. Open your web browser and go to `http://localhost:3000`. You should see your product listing page displaying the product information.

Adding Product Details Page

Let’s add a product details page. This page will display more information about a specific product. Create a file named `pages/products/[id].tsx` (or `pages/products/[id].js` if you are not using TypeScript) and add the following code:


// pages/products/[id].tsx
import { GetStaticProps, GetStaticPaths } from 'next';
import Image from 'next/image';
import { useRouter } from 'next/router';
import { Product } from '../../types/product';
import { getProducts } from '../../lib/products';

interface Props {
  product: Product;
}

const ProductDetails: React.FC = ({ product }) => {
  const router = useRouter();

  if (router.isFallback) {
    return <div>Loading...</div>;
  }

  return (
    <div>
      <h1>{product.name}</h1>
      <div>
        <div>
          
        </div>
        <div>
          <p>{product.description}</p>
          <p>${product.price}</p>
          {/* Add more product details here */} 
        </div>
      </div>
    </div>
  );
};

export const getStaticPaths: GetStaticPaths = async () => {
  const products = await getProducts();
  const paths = products.map((product) => ({
    params: { id: product.id.toString() },
  }));

  return {
    paths,
    fallback: true, // or 'blocking'
  };
};

export const getStaticProps: GetStaticProps = async ({ params }) => {
  const products = await getProducts();
  const product = products.find((product) => product.id === parseInt(params?.id as string));

  if (!product) {
    return {
      notFound: true,
    };
  }

  return {
    props: { product },
    revalidate: 10,
  };
};

export default ProductDetails;

Let’s break down this code:

  • Imports: We import `GetStaticProps`, `GetStaticPaths` from ‘next’, `useRouter` from ‘next/router’, the `Product` interface, and the `getProducts` function.
  • Props Interface: We define an interface `Props` to specify the type of the `product` prop.
  • ProductDetails Component: This component receives a single `product` prop. It uses the `useRouter` hook to access the router object, which provides information about the current route. The `router.isFallback` check handles the situation where the page is being generated on demand (due to `fallback: true` in `getStaticPaths`), showing a loading state while the data is fetched.
  • getStaticPaths: This function is responsible for pre-rendering all possible paths for the product details pages. It fetches all products, maps them to paths with the product ID as a parameter, and returns them. The `fallback: true` option allows Next.js to generate the pages on demand if a path isn’t pre-rendered.
  • getStaticProps: This function fetches the product data based on the ID from the route parameters (`params.id`). It finds the corresponding product and passes it as a prop to the `ProductDetails` component. If the product isn’t found, it returns a `notFound: true` to display a 404 page.

Linking to the Product Details Page

Now, let’s link each product in the product listing page to its details page. Modify the `pages/index.tsx` file to include a link to the product details page using Next.js’s `Link` component:


// pages/index.tsx
import { GetStaticProps } from 'next';
import Image from 'next/image';
import Link from 'next/link'; // Import the Link component
import { Product } from '../types/product';
import { getProducts } from '../lib/products';

interface Props {
  products: Product[];
}

const Home: React.FC = ({ products }) => {
  return (
    <div>
      <h1>Our Products</h1>
      <div>
        {products.map((product) => (
          <div>
            
              <a>
                
                <h2>{product.name}</h2>
              </a>
            
            <p>{product.description}</p>
            <p>${product.price}</p>
          </div>
        ))}
      </div>
    </div>
  );
};

export const getStaticProps: GetStaticProps = async () => {
  const products = await getProducts();
  return {
    props: { products },
    revalidate: 10,
  };
};

export default Home;

We import the `Link` component from `next/link` and wrap the product image and name within a `Link` component. The `href` attribute specifies the URL to link to, constructed using the product’s ID. The `` tag inside the `Link` is important for accessibility and proper linking behavior.

Common Mistakes and How to Fix Them

Here are some common mistakes beginners make when building Next.js applications and how to avoid them:

  • Incorrect File Structure: Make sure your files are in the correct directories (e.g., components in a `components` folder, types in a `types` folder, etc.). A well-organized file structure makes your code easier to manage and understand.
  • Forgetting to Import Components: Always remember to import the components you’re using. This is especially important when you’re working with custom components.
  • Not Using `getStaticProps` or `getServerSideProps` Correctly: Understand the difference between `getStaticProps` (for static site generation) and `getServerSideProps` (for server-side rendering). Use `getStaticProps` whenever possible for better performance and SEO. Use `getServerSideProps` when you need to fetch data that changes frequently or requires authentication.
  • Ignoring TypeScript Errors: If you’re using TypeScript, pay close attention to the type errors. They can save you a lot of debugging time. Configure your IDE (like VS Code) to show errors in real-time.
  • Not Optimizing Images: Use the `next/image` component to optimize images. This component automatically optimizes images for different devices, improving performance. Make sure you configure the image domains in your `next.config.js` file if your images are hosted on a different domain.
  • Not Handling Errors Properly: Implement proper error handling to catch and display errors gracefully. Use `try…catch` blocks when fetching data and display user-friendly error messages.
  • Overlooking Accessibility: Ensure your website is accessible to everyone. Use semantic HTML, provide alt text for images, and ensure sufficient color contrast.

Key Takeaways

  • Next.js is a powerful framework for building performant and SEO-friendly e-commerce applications.
  • Use `getStaticProps` for static content and `getServerSideProps` for dynamic content.
  • The `next/image` component is essential for image optimization.
  • Properly organize your code and handle errors.
  • Always consider accessibility.

SEO Best Practices

To optimize your product listing page for search engines, consider the following SEO best practices:

  • Keyword Research: Identify relevant keywords for your products and incorporate them naturally into your page title, headings, meta description, and content.
  • Page Title: Create a descriptive and keyword-rich page title (e.g., “Awesome T-Shirt – Buy Now | Your Store”). Keep it concise, ideally under 60 characters.
  • Meta Description: Write a compelling meta description that accurately summarizes your page’s content and includes relevant keywords. Keep it under 160 characters.
  • Heading Tags (H1-H6): Use heading tags to structure your content logically and highlight important keywords. Use only one `H1` tag per page.
  • Image Optimization: Use descriptive alt text for your images. Compress images to reduce file size.
  • Internal Linking: Link to other relevant pages on your website to improve site navigation and distribute link juice.
  • Mobile Responsiveness: Ensure your website is responsive and displays correctly on all devices.
  • Site Speed: Optimize your website’s loading speed by using techniques like code splitting, image optimization, and caching.

Remember to use keywords naturally and avoid keyword stuffing, which can harm your search engine rankings.

Building a product listing page in Next.js is a fundamental step toward creating a full-fledged e-commerce website. You’ve learned how to set up a Next.js project, fetch and display product data, create a product details page, and link between pages. By mastering these concepts and applying SEO best practices, you’re well on your way to building a successful online store. The power of Next.js lies in its flexibility and performance, allowing you to create engaging and efficient user experiences. As you expand your skills, consider adding features like product filtering, sorting, and a shopping cart to enhance your e-commerce application. The possibilities are endless, and with each feature you add, you’ll be one step closer to a complete and thriving online store.