Build a Simple Next.js Interactive Web-Based Product Catalog

Written by

in

In today’s digital marketplace, showcasing products effectively is crucial for any online business. A well-designed product catalog not only presents your offerings but also enhances the user experience, making it easier for potential customers to find and purchase what they need. This tutorial will guide you through building a simple, yet functional, interactive web-based product catalog using Next.js, a powerful React framework.

Why Next.js?

Next.js offers several advantages for this project:

  • Server-Side Rendering (SSR) and Static Site Generation (SSG): Improve SEO and initial page load times.
  • Built-in Routing: Simplifies navigation within your catalog.
  • API Routes: Easily handle data fetching and backend interactions.
  • Optimized Performance: Next.js automatically optimizes images and code splitting.

Project Overview

Our product catalog will feature the following:

  • A list of products displayed with images, names, and prices.
  • A basic filtering system to sort products by category.
  • A clean and responsive user interface.

Prerequisites

Before we begin, make sure you have the following installed:

  • Node.js (version 14 or higher)
  • npm or yarn (package manager)
  • A code editor (e.g., VS Code)

Step-by-Step Guide

1. Setting Up the Next.js Project

Let’s start by creating a new Next.js project. Open your terminal and run the following command:

npx create-next-app product-catalog

This command will create a new directory named “product-catalog” and set up a basic Next.js project structure. Navigate into the project directory:

cd product-catalog

2. Project Structure and File Setup

Your project directory should look similar to this:

product-catalog/
├── node_modules/
├── pages/
│   ├── _app.js
│   ├── index.js
│   └── api/
│       └── hello.js
├── public/
│   └── ...
├── .gitignore
├── next.config.js
├── package.json
└── README.md

We’ll be working primarily within the pages directory. The index.js file in the pages directory is the entry point for your application’s homepage. We’ll create the following files and components:

  • components/ProductCard.js: To display individual product details.
  • components/ProductList.js: To render the list of products.
  • components/Filter.js: To handle product filtering.
  • data/products.json: To store our product data (we’ll create this later).

3. Creating Product Data (products.json)

In the data directory, create a file named products.json. This file will store our product data in JSON format. Here’s an example of how the data might look:

[
  {
    "id": 1,
    "name": "Product A",
    "category": "Electronics",
    "price": 99.99,
    "image": "/images/product-a.jpg"
  },
  {
    "id": 2,
    "name": "Product B",
    "category": "Clothing",
    "price": 29.99,
    "image": "/images/product-b.jpg"
  },
  {
    "id": 3,
    "name": "Product C",
    "category": "Electronics",
    "price": 149.99,
    "image": "/images/product-c.jpg"
  },
  {
    "id": 4,
    "name": "Product D",
    "category": "Books",
    "price": 19.99,
    "image": "/images/product-d.jpg"
  }
]

Make sure to replace the image paths with the actual paths to your image files within the public/images directory. You can add more products to this JSON file.

4. Creating the ProductCard Component (components/ProductCard.js)

Create a file named ProductCard.js inside the components directory. This component will display the details of a single product.

import Image from 'next/image';

function ProductCard({ product }) {
  return (
    <div>
      
      <h3>{product.name}</h3>
      <p>Category: {product.category}</p>
      <p>Price: ${product.price}</p>
    </div>
  );
}

export default ProductCard;

Here, we import the Image component from `next/image` for optimized image loading. We then render the product’s image, name, category, and price. We’ll add styling later.

5. Creating the ProductList Component (components/ProductList.js)

Create a file named ProductList.js inside the components directory. This component will render a list of ProductCard components.

import ProductCard from './ProductCard';

function ProductList({ products }) {
  return (
    <div>
      {products.map((product) => (
        
      ))}
    </div>
  );
}

export default ProductList;

This component receives an array of products as a prop and maps over it, rendering a ProductCard for each product. The `key` prop is essential for React to efficiently update the list.

6. Creating the Filter Component (components/Filter.js)

Create a file named Filter.js inside the components directory. This component will handle filtering products by category.

function Filter({ categories, selectedCategory, onCategoryChange }) {
  return (
    <div>
      <label>Filter by Category:</label>
       onCategoryChange(e.target.value)}
      >
        All
        {categories.map((category) => (
          
            {category}
          
        ))}
      
    </div>
  );
}

export default Filter;

This component displays a dropdown select element. It receives an array of categories, the currently selected category, and a function to handle category changes (onCategoryChange). The `onChange` event updates the selected category in the parent component.

7. Implementing the Main Page (pages/index.js)

Now, let’s modify the pages/index.js file to bring everything together. Replace the existing content with the following:

import { useState, useEffect } from 'react';
import ProductList from '../components/ProductList';
import Filter from '../components/Filter';

function HomePage() {
  const [products, setProducts] = useState([]);
  const [selectedCategory, setSelectedCategory] = useState('');
  const [categories, setCategories] = useState([]);

  useEffect(() => {
    async function fetchData() {
      const res = await fetch('/api/products');
      const data = await res.json();
      setProducts(data);

      // Extract unique categories
      const uniqueCategories = [...new Set(data.map(product => product.category))];
      setCategories(uniqueCategories);
    }

    fetchData();
  }, []);

  const filteredProducts = products.filter((product) => {
    if (!selectedCategory) return true;
    return product.category === selectedCategory;
  });

  const handleCategoryChange = (category) => {
    setSelectedCategory(category);
  };

  return (
    <div>
      <h1>Product Catalog</h1>
      
      
    </div>
  );
}

export default HomePage;

Here’s a breakdown of what’s happening:

  • We import the necessary components and the useState and useEffect hooks from React.
  • We use useState to manage the list of products, the selected category, and the list of available categories.
  • The useEffect hook fetches the product data from our API route (we’ll create this in the next step) when the component mounts.
  • We extract unique categories from the product data.
  • The filteredProducts variable filters the products based on the selected category.
  • The handleCategoryChange function updates the selected category.
  • We render the Filter and ProductList components, passing the necessary props.

8. Creating the API Route (pages/api/products.js)

Create a file named products.js inside the pages/api directory. This will be our API route to fetch product data. This is where we’ll read the data from the products.json file.

import { promises as fs } from 'fs';
import path from 'path';

export default async function handler(req, res) {
  const jsonDirectory = path.join(process.cwd(), 'data');
  const fileContents = await fs.readFile(jsonDirectory + '/products.json', 'utf8');

  res.status(200).json(JSON.parse(fileContents));
}

This code does the following:

  • Imports the fs module for file system operations.
  • Imports the path module to construct file paths.
  • Defines an asynchronous handler function that takes a request (req) and a response (res) object as arguments.
  • Uses path.join to construct the full path to the products.json file.
  • Reads the contents of the products.json file using fs.readFile.
  • Parses the JSON data using JSON.parse.
  • Sets the HTTP status code to 200 (OK) and sends the parsed JSON data in the response body using res.json.

9. Adding Styling (styles/globals.css)

Now, let’s add some basic styling to make our product catalog visually appealing. Open the styles/globals.css file and add the following CSS:

/* styles/globals.css */
body {
  font-family: sans-serif;
  margin: 0;
  padding: 20px;
  background-color: #f4f4f4;
}

.container {
  max-width: 1200px;
  margin: 0 auto;
  background-color: #fff;
  padding: 20px;
  border-radius: 8px;
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}

h1 {
  text-align: center;
  margin-bottom: 20px;
}

.filter {
  margin-bottom: 20px;
}

.product-list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 20px;
}

.product-card {
  border: 1px solid #ddd;
  padding: 15px;
  border-radius: 8px;
  text-align: center;
  background-color: #fff;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
}

.product-card img {
  margin-bottom: 10px;
  border-radius: 4px;
  max-width: 100%;
  height: auto;
}

.filter select {
  padding: 8px 12px;
  border: 1px solid #ccc;
  border-radius: 4px;
  font-size: 1rem;
}

This CSS provides basic styling for the layout, headings, filter, and product cards. You can customize the styles to match your design preferences.

10. Running the Application

Now, it’s time to run your application. Open your terminal, navigate to your project directory, and run the following command:

npm run dev  # or yarn dev

This will start the development server. Open your web browser and go to http://localhost:3000. You should see your product catalog, displaying the products from your products.json file, and you should be able to filter by category.

Common Mistakes and How to Fix Them

1. Incorrect File Paths

Mistake: Images not displaying, or the API route not fetching data. This is often caused by incorrect file paths.

Solution: Double-check all file paths. Make sure image paths in products.json start with a forward slash (/) and correctly point to the images in the public directory. Verify the API route is correctly reading the products.json file path.

2. Missing Dependencies

Mistake: Errors related to missing dependencies, especially when using features like the Image component.

Solution: Ensure that you have installed all necessary dependencies. Typically, Next.js handles most dependencies automatically, but if you encounter issues, run npm install or yarn install in your project directory to install all dependencies specified in your package.json file.

3. CORS Issues

Mistake: If you’re fetching data from an external API (not in this example, but a common scenario), you might encounter Cross-Origin Resource Sharing (CORS) errors.

Solution: If you control the external API, configure it to allow requests from your domain. If you don’t control the API, you can use a proxy server or a serverless function to make requests on the server-side, bypassing the CORS restrictions in your client-side code.

4. Typos in Component Names or Prop Names

Mistake: Components not rendering or data not displaying correctly due to typos.

Solution: Carefully review your component names and prop names for any typos. Ensure that you are passing the correct data and props to each component.

5. Data Fetching Errors

Mistake: Product data not loading, or errors in the console related to data fetching.

Solution: Inspect the console for error messages. Check the API route to ensure it’s correctly reading and parsing the products.json file. Use console.log statements to debug the data fetching process and verify that data is being retrieved and passed to components correctly.

Key Takeaways

  • Project Structure: Understanding the structure of a Next.js project is fundamental.
  • Component-Based Architecture: Breaking down the UI into reusable components (ProductCard, ProductList, Filter) makes the code more organized and maintainable.
  • Data Fetching: Using useEffect to fetch data and API routes to serve the data.
  • Dynamic Rendering: Using map to render dynamic content based on data.
  • Optimized Images: Utilizing the next/image component for optimized image loading and performance.
  • Filtering Implementation: Implementing a basic filter functionality to enhance user interaction.

FAQ

1. How can I add more features to this product catalog?

You can add features such as product detail pages, a shopping cart, user authentication, search functionality, pagination, and more. You could also integrate with a backend database to store and manage product data.

2. How do I deploy this application?

Next.js applications can be deployed to various platforms, including Vercel (recommended, as it’s built by the Next.js creators), Netlify, AWS, and others. Vercel provides a streamlined deployment process.

3. How can I improve the performance of my product catalog?

Use optimized images, code splitting, and server-side rendering (which Next.js handles automatically) to improve performance. Consider lazy loading images and data. Minimize the amount of data transferred and optimize any database queries if you are using one.

4. Can I use a different data source besides a JSON file?

Yes, you can fetch product data from a database (e.g., PostgreSQL, MongoDB), a third-party API, or any other data source. You’ll need to modify the API route (pages/api/products.js) to fetch the data from your chosen data source.

5. How can I add more complex filtering options?

You can expand the filter component to include options for filtering by price range, brand, or other product attributes. You would need to update the Filter component to handle these new filter options and modify the filteredProducts logic in index.js to apply the filters accordingly.

Building a product catalog with Next.js is a fantastic way to learn about modern web development principles and create a practical, user-friendly application. By following this tutorial, you’ve taken the first steps in creating a dynamic and engaging product catalog that can be easily customized and expanded to meet your specific needs. Remember that the foundation you’ve built here is easily extensible, and you can add many more features to create a more sophisticated and compelling user experience. The concepts learned here – component composition, data fetching, and dynamic rendering – are the building blocks of many modern web applications, and mastering them will serve you well in your journey as a web developer. Keep experimenting, keep learning, and keep building!