reactjstypescriptnext.jsnext.js13nextjs-dynamic-routing

Getting undefined params in my compoment with generateStaticParams nextjs 13. How to pass them correctly?


I'm using the App router of nextjs 13, with typescript, and I'm trying to create dynamic pages and generate the paths for them with generateStaticParams().

The function generateStaticParams() seems to work, and it collects the correct information. But when I want to use the information in my component, I get a undefined value.

The page is fetchting the data correctly if I hardcode the ID, so I only need to swap this for the ID collected with the generateStaticParams.

How can I make sure that the page is receiving the data from generateStaticParams?

/src/app/coffeeplaces/[coffeeplace]/page.tsx
import fetchCoffeePlace from "@/api/fetchCoffeeplace";
import { db } from "@/helpers/firebaseConfig";
import { collection, getDocs } from "firebase/firestore";
import Image from "next/image";
import { Suspense } from "react";
import ImagesCarousel from "./components/imagesCarousel";
import Link from "next/link";
import CoffeeMainDetails from "./components/coffeeMainDetails";

export default async function Page({ params }: { params: { id: string, slug: string } }) {
  const { id, slug } = params

  // this comes back UNDEFINED
  console.log('id', params.id) // or id
  console.log('slug', params.slug) // or slug

  // This hardcoded option works to fetch the data
  // const id = 'r7CFv3t4Ni5sNl20wE8h'

  const CoffeeplaceData = fetchCoffeePlace(id);
  const CoffeeplaceInfoData = fetchCoffeePlace(id); // 

  const coffeeplace = await CoffeeplaceData;

  return (
    <div>
      <Link href={'/coffeeplaces'} className="text-sm font-light">Back to listing</Link>
      <Suspense fallback={<div>Loading...</div>}>
        <CoffeeplaceInfo promise={CoffeeplaceInfoData} />
      </Suspense>
    </div>
  );
}

async function CoffeeplaceInfo({ promise, }: { promise: Promise<CoffeePlace>; }) {
  const coffeeplaceInfo = await promise;
  return (
      <>
      <div className="pt-6 lg:flex w-full">
        <ImagesCarousel featuredImage={coffeeplaceInfo.featuredImage} />
        <CoffeeMainDetails name={coffeeplaceInfo.name} priceRange={coffeeplaceInfo.priceRange} organic={coffeeplaceInfo.organic} fairTrade={coffeeplaceInfo.fairTrade}/>
      </div>
      </>
    )
}

export async function generateStaticParams() { // tried with this: generateStaticParams({ params: { id, slug }}: any)
  const coffeePlaces = await getDocs(collection(db, "coffeePlaces"));
  const data = coffeePlaces.docs.map((doc) => ({
    id: doc.id,
    ...doc.data()
  }));

  var slugify = require('slugify')

  const result = data.map((item) => {
    const slug = slugify(item.name, {
      replacement: '-',  
      lower: true,  
      strict: true,
    });

    // these are logging the correct values
    console.log(item.id);
    console.log(slug);
    
    return {
      params: {
        id: item.id,
        slug: slug
      }
    };
  }); 

  // this is logging the correct result
  // [{ params: { id: '9ZVOCYsngTksBKXqQQOH', slug: 'coffeeplace-1' } }, { params: { id: 'r7CFv3t4Ni5sNl20wE8h', slug: 'example-2' } } ]

  return result; 
}

Using parallel Data Fetching

Small other thing, might be related, in the generateStaticParams function, when I loop over the result, for the item.name it says: Property 'name' does not exist on type '{ id: string; }')

Edit: Trying out with new environment

I've created a new nextjs project to test this out, and got the same results.

  1. I installed Nextjs 13 app router, typescript and tailwind with npx create-next-app@latest my-project --typescript --eslint
  2. I created a folder structure like so src/app/books/[type]/[book-id]/page.tsx, and used the following example to test it: Example of generateStaticParams

When I log the results I get an unidentified and when I build with npm run build it doesn't show the build of the pages.

Sandbox example


Solution

  • You have mismatch between dynamic segment name and what you expect in the code. The naming should be consistent between folder name and what you expect in the code.

    You have coffeeplace segment (/src/app/coffeeplaces/[coffeeplace]/page.tsx) in the file system, but for some reason you want to pass id and slug keys from the generateStaticParams function. You can't do that, the key that you are passing from generateStaticParams should also be named coffeeplace and in your case it's the single key, you can't pass more info, other keys etc.

    Info from the docs:

    generateStaticParams should return an array of objects where each object represents the populated dynamic segments of a single route.

    Each property in the object is a dynamic segment to be filled in for the route. The properties name is the segment's name, and the properties value is what that segment should be filled in with.

    /product/[id] -> { id: string }[]

    /products/[category]/[product] -> { category: string, product: string }[]

    /products/[...slug] -> { slug: string[] }[]

    More in the docs