creating dynamic routes with Next.js

I am in the process of creating a Next.js application that showcases a product catalog sorted by categories and subcategories. The category and subcategory information is stored in an array of objects structured like this:

const categories = [
  {
    id: 1,
    name: "Category A",
    subcategories: [
      {
        id: 1,
        name: "Subcategory A1",
        subsubcategories: [
          {
            id: 1,
            name: "Sub-Subcategory A1.1",
          },
          {
            id: 2,
            name: "Sub-Subcategory A1.2",
          },
        ],
      },
      // Other subcategories and sub-subcategories within Category A
    ],
  },
  {
    id: 2,
    name: "Category B",
    subcategories: [
      // Subcategories and sub-subcategories within Category B
    ],
  },
  // Other categories with their respective subcategories and sub-subcategories
];

I aim to create dynamic paths for each product that adhere to the format category/subcategory/subsubcategory/productID. For instance, if a user selects a product with ID 123 in Category A, Subcategory A1, and Sub-Subcategory A1.1, the URL should appear as follows: /category-a/subcategory-a1/sub-subcategory-a1-1/123.

The product details are stored in a separate array of objects shown below:

const products = [
  {
    id: 123,
    name: "Product A1.1.1",
    category_id: 1,
    subcategory_id: 1,
    subsubcategory_id: 1,
    description: "Lorem ipsum dolor sit amet",
    price: 9.99,
    image: "/images/product-a1-1-1.jpg",
  },]

Answer №1

Give this a shot:

// file:

/pages/[category]/[subcategory]/[subsubcategory]/[product].js

import React from 'react'
import { useRouter } from 'next/router'

    const items = [
      {
        id: 1,
        name: "Banana",
        category: 1,
        subcategory: 1,
        subsubcategory: 1,
      },
      {
        id: 2,
        name: "Foobar",
        category: 2,
        subcategory: 3,
        subsubcategory: 5,
      }
    ];
    
    
    const categories = [
      {
        id: 1,
        name: "Fruits",
        subcategories: [
          {
            id: 1,
            name: "Tropical fruits",
            subsubcategories: [
              {
                id: 1,
                name: "Berry fruits", // yes a banana is really a berry fruit
              },
              {
                id: 2,
                name: "Sub-Subcategory A1.2",
              },
            ],
          },
          {
            id: 2,
            name: "Subcategory A2",
            subsubcategories: [
              {
                id: 3,
                name: "Sub-Subcategory A2.1",
              },
              {
                id: 4,
                name: "Sub-Subcategory A2.2",
              },
            ],
          },
        ],
      },
      {
        id: 2,
        name: "Category B",
        subcategories: [
          {
            id: 3,
            name: "Subcategory B1",
            subsubcategories: [
              {
                id: 5,
                name: "Sub-Subcategory B1.1",
              },
              {
                id: 6,
                name: "Sub-Subcategory B1.2",
              },
            ],
          },
          {
            id: 4,
            name: "Subcategory B2",
            subsubcategories: [
              {
                id: 7,
                name: "Sub-Subcategory B2.1",
              },
              {
                id: 8,
                name: "Sub-Subcategory B2.2",
              },
            ],
          },
        ],
      },
    ];
    
    const slugify = str =>
      str
        .toLowerCase()
        .trim()
        .replace(/[^\w\s-]/g, '')
        .replace(/[\s_-]+/g, '-')
        .replace(/^-+|-+$/g, '');
    
    export default function Product() {
    
      const router = useRouter()
    
      const { category, subcategory, subsubcategory, product } = router.query
      console.log(category, subcategory, subsubcategory, product);
      // {category: 'fruits', subcategory: 'tropical-fruits', subsubcategory: 'berry-fruits', product: 'banana'}
    
    
      const found = items.find((item) => slugify(item.name) === product); // banana === banana
      console.log({ found });
      // {id: 1, name: 'Banana', category: 1, subcategory: 1, subsubcategory: 1}
    
      return (
        <div>{found.name}</div>
      )
    }
    
    export async function getStaticPaths() {
    
      const paths = items.map((item) => {
        // find the right categories for this product item
        let categoryFound = categories.find((c) => c.id == item.category);
        let subcategoryFound = categoryFound.subcategories.find((sc) => sc.id == item.subcategory);
        let subsubcategoryFound = subcategoryFound.subsubcategories.find((ssc) => ssc.id == item.subsubcategory);
    
        return {
          params: {
            product: slugify(item.name), // the slug
            category: slugify(categoryFound.name),
            subcategory: slugify(subcategoryFound.name),
            subsubcategory: slugify(subsubcategoryFound.name)
          }
        }
      });
    
      return {
        paths,
        fallback: false,
      };
    
    }
    
    export async function getStaticProps({ params }) {
    
      const { category, subcategory, subsubcategory, product } = params;
    
      return {
        props: {
          category,
          subcategory,
          subsubcategory,
          product,
        },
      };
    }

The building process also constructs the product pages with the accurate categories: image

You can access your products like this:

http://localhost:3000/fruits/tropical-fruits/berry-fruits/banana

Answer №2

Here's the concept:

  1. Create pages for pages/ or app/ before building with NextJs (using a prepare script)

  2. Rethink the nesting structure: excessive subcategories may not be ideal, consider using recursion for a cleaner and more efficient codebase

I'm thinking about proposing a prepare feature for NextJS to automate page generation, it seems like a missing piece.

Update: The code is error-free but hasn't been tested yet:

import mkdirp from "mkdirp";

type ArticleId = number;
type CategoryId = number;

interface Article {
  id: ArticleId;
  name: string;
  category: CategoryId[];
}

interface Category {
  id: CategoryId;
  name: string;
  categories?: Category[];
}

// @proposal consider a flattened structure
const articles: Article[] = [
  {
    id: 1000,
    name: "banana",
    category: [1, 1, 2],
  },
  {
    id: 2000,
    name: "strawberry",
    category: [1, 1, 1],
  },
];

// @proposal always use 'sub' or like here 'categories' (no subsubsub)
const categories: Category[] = [
  {
    id: 1,
    name: "fruits",
    categories: [
      {
        id: 11,
        name: "berries", // 🫐🍓
        categories: [{ id: 111, name: "swiss berries" }], // 🇨🇭🍓
      },
      {
        id: 12,
        name: "bananas",
        categories: [{ id: 121, name: "argentina bananas" }], // 🇦🇷🍌
      },
    ],
  },
];

const paths: string[] = [];

// Function to find deepest subCategory recursively
function recursive(category: Category, currentPath = ""): void {
  const subCategories = category.categories;

  if (!subCategories)
    return void paths.concat([`${currentPath}/[${category.name}]`);

  subCategories.forEach((subCategory) =>
    recursive(subCategory, `${currentPath}/[${subCategory.name}]`)
  );
}

categories.forEach((category) => recursive(category));

paths.forEach((path) => mkdirp.sync(`${path}/[productId]`));

The code can be optimized, but since it runs before NextJs build, it's not crucial right now. You may need to revisit it based on the number of articles in the future.

Depending on app/ or pages/, you can access req.query at:

  • [productId]/page.tsx (app/ directory)
  • [productId]/index.tsx (pages/ directory)

You can also include the index/page.tsx file in the loop and import the code to display the product (same code for all products in every category).

On a random note about marketing, consider rounding prices to whole numbers instead of using .99. It may give a sense of transparency and fairness to customers.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What is the best way to create a props interface that includes state and setState values that are being

As I dive deeper into TypeScript, a hurdle has appeared in my app. Upon introducing React.FunctionComponent to my components, an issue arose with my Props interface. The error message reads: Type '({ value, setValue, }: Props) => JSX.Element' ...

Timing with Three.js - the key to engaging interactive experiences

Lately, I've been discovering some amazing interactive shorts created with three.js. Take a look at this example: I'm curious about the timing mechanism used in these creations. Are there any known libraries for that? The synchronization of mu ...

disableDefault not functioning properly post-fadeout, subsequent loading, and fade-in of new content with a different URL into

After extensive searching, I still haven't found a solution to my problem. My task involves bringing in HTML pages into a div element. I managed to make the content fade out, load new href content, and then fade in the new content. However, I'm ...

What is the best way to calculate the total sum of values from the third-tier nested array of objects in all documents stored

One of my mongoose documents contains the following Schema: Products { "section":"", "category":"Food & Drink", "sub_category":"Main Dish", "product_code":"ST", "title":"Steak", "description":"Served with sauted vegetables", "tags":[ ...

JavaScript code to retrieve and store data from API endpoints

I am working with a Web API endpoint that provides the location of a URL (). When a button is clicked, this API is called and a URL is returned. The file format could be PDF, Excel, or Word. I am looking for a way to download the file and save it to disk ...

Exploring Angular.js: How to propagate model changes from a child controller within an ng-repeat loop?

I'm facing an issue in my Angular application where changes made to data inside a child controller's form are not being reflected back in the parent array after saving using Restangular. I have tried using debounce to auto-save the data, but it s ...

A numeric input area that only accepts decimal numbers, with the ability to delete and use the back

I have successfully implemented a code for decimal numbers with only two digits after the decimal point. Now, I am looking to enhance the code in the following ways: Allow users to use backspace and delete keys. Create a dynamic code that does not rely o ...

Adjust the position of vertices when the mouse hovers over them

I have a PlaneGeometry and I am trying to adjust the z position of the vertex being hovered over, but I am uncertain about how to retrieve it. //THREE.WebGLRenderer 69 // Creating plane var geometryPlane = new THREE.PlaneGeometry( 100, 100, 20, 10 ); ...

Load Angular template dynamically within the Component decorator

I am interested in dynamically loading an angular template, and this is what I have so far: import { getHTMLTemplate } from './util'; const dynamicTemplate = getHTMLTemplate(); @Component({ selector: 'app-button', // templat ...

Encountering an error with Nested MaterialUI Tabs while attempting to open the second level of tabs

I am attempting to create nested horizontal tabs using MaterialUI. This means having a first level of tabs that, when clicked on, opens a second level of tabs. Here is a link to a working example of the code: https://codesandbox.io/s/sweet-pasteur-x4m8z?f ...

Sending information to a PHP script using Ajax

I am working on a project that involves input fields sending data to a PHP page for processing, and then displaying the results without reloading the page. However, I have encountered an issue where no data seems to be passed through. Below is my current s ...

Encountering an Issue in Angular 5: Trouble Retrieving Information from Local JSON Source [object Object]

Trying to fetch data from a file named sampledata.json Issues: {{sampledata}} is showing [object Object] {{sampleDataModel.Title}} is throwing an error: TypeError: Cannot read property 'Title' of undefined Contents of sampledata.json: { ...

What is the best way to switch between three different menus and ensure that the last selected menu remains open when navigating to a new page

My knowledge of javascript, jquery, and php is pretty much non-existent, unfortunately. I only have a grasp on html and css at the moment. However, I will be starting school to learn these languages in the fall! The issue I am currently facing is creating ...

Encountered a Soundcloud embedded javascript issue: Unable to execute the 'createPattern' function on 'CanvasRenderingContext2D' due to the canvas width being 0

Encountering an Uncaught InvalidStateError with embedded SoundCloud profiles from (https://w.soundcloud.com/player/widget-6c3640e3.js & https://w.soundcloud.com/player/multi-sounds-0e392418.js) on my website pullup.club hosted on github.io and is open ...

Component failing to refresh with each key modification

My understanding is that adding a key attribute to a component should make it reactive when the key changes. However, with a v-navigation-drawer from Vuetify, this doesn't seem to have any impact. I've tried making arbitrary changes to the logge ...

Why does the ng-click function fail to execute when using the onclick attribute in AngularJS?

Whenever I try to invoke the ng-click function using onClick, I encounter an issue where the ng-click function is not being called. However, in my scenario, the model does open with the onClick function. //Function in Controller $scope.editProductDetail ...

Guide on simulating an incoming http request (response) using cypress

Is there a way to mock a response for an HTTP request in Cypress? Let me demonstrate my current code: Cypress.Commands.add("FakeLoginWithMsal", (userId) => { cy.intercept('**/oauth2/v2.0/token', (req) => { ...

I am currently working on incorporating Azure AD B2C authentication into my Next.js application, but I keep encountering an issue where I receive an error stating that the challenge

Recently, I encountered an issue while integrating Azure AD B2C using the Next Auth package. An error related to code challenge was thrown, and I have provided a screenshot of it. https://i.sstatic.net/aeECD.png Upon further research into SPA documentati ...

What steps can be taken to convert a list box into an editable box?

I created a listbox using a table because I needed to display two columns for the list. Now, I am attempting to enable editing the text directly within the table when I select an item from the listbox. Essentially, I want the selected item to become an edi ...

Customizing animations for birds

I'm currently in the process of developing a new website. The link to access the site is: One thing I am eager to achieve is having birds from this specific link incorporated into my background: I have attempted various methods without success. If a ...