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;
}
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.
npx create-next-app@latest my-project --typescript --eslint
src/app/books/[type]/[book-id]/page.tsx
, and used the following example to test it: Example of generateStaticParamsWhen 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.
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[] }[]