next.js
next.js copied to clipboard
Next 13 - generateStaticParams not working with Multiple Dynamic Segments in a Route
Verify canary release
- [X] I verified that the issue exists in the latest Next.js canary release
Provide environment information
Reproduced in multiple envs, but pasting the GitHub Codespace
Operating System:
Platform: linux
Arch: x64
Version: #100~18.04.1-Ubuntu SMP Mon Oct 17 11:44:30 UTC 2022
Binaries:
Node: 16.18.0
npm: 8.19.2
Yarn: 1.22.19
pnpm: N/A
Relevant packages:
next: 13.0.3
eslint-config-next: 13.0.3
react: 18.2.0
react-dom: 18.2.0
What browser are you using? (if relevant)
No response
How are you deploying your application? (if relevant)
No response
Describe the Bug
I am following the new docs for generating pages with Multiple Dynamic Segments in a Route (https://beta.nextjs.org/docs/api-reference/generate-static-params#generate-segments-from-the-top-down).
There is an issue when generating the child routes of the second dynamic route.
I am providing a minimum reproduction in a clean repo which also experiences the issue: https://github.com/ClemannD/next-13-test-repo. (this was created using npx create-next-app@latest --experimental-app
)
here is the output from npm run build
:
@ClemannD ➜ /workspaces/next-13-test-repo (main ✗) $ npm run build
> [email protected] build
> next build
warn - You have enabled experimental feature (appDir) in next.config.js.
warn - Experimental features are not covered by semver, and may cause unexpected or broken application behavior. Use at your own risk.
info - Thank you for testing `appDir` please leave your feedback at https://nextjs.link/app-feedback
info - Creating an optimized production build
info - Compiled successfully
info - Linting and checking validity of types
info - Collecting page data ..categorySlug undefined
Error: A required parameter (categorySlug) was not provided as a string in generateStaticParams for /[categorySlug]/[productId]
at /workspaces/next-13-test-repo/node_modules/next/dist/build/utils.js:656:27
at Array.forEach (<anonymous>)
at /workspaces/next-13-test-repo/node_modules/next/dist/build/utils.js:649:29
at Array.forEach (<anonymous>)
at buildStaticPaths (/workspaces/next-13-test-repo/node_modules/next/dist/build/utils.js:620:17)
at buildAppStaticPaths (/workspaces/next-13-test-repo/node_modules/next/dist/build/utils.js:786:16)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at async /workspaces/next-13-test-repo/node_modules/next/dist/build/utils.js:878:119
at async Span.traceAsyncFn (/workspaces/next-13-test-repo/node_modules/next/dist/trace/trace.js:79:20)
> Build error occurred
Error: Failed to collect page data for /[categorySlug]/[productId]
at /workspaces/next-13-test-repo/node_modules/next/dist/build/utils.js:959:15
at processTicksAndRejections (node:internal/process/task_queues:96:5) {
type: 'Error'
}
info - Collecting page data
These are the files i have
// app/[categorySlug]/page.tsx
export default async function CategoryPage({
params,
}: {
params: { categorySlug: string };
}) {
return <div>{params.categorySlug}</div>;
}
export async function generateStaticParams() {
return [
{
categorySlug: "food",
},
{
categorySlug: "materials",
},
];
}
// app/[categorySlug]/[productId]/page.tsx
export default async function ProductPage({
params,
}: {
params: { productId: string };
}) {
console.log("params", params);
return <div></div>;
}
export async function generateStaticParams({ categorySlug }: any) {
console.log("categorySlug", categorySlug);
return [
{
productId: "1",
},
{
productId: "2",
},
];
}
Expected Behavior
According to the docs, this is the way to generate the following routes:
- /food/1
- /food/2
- /materials/1
- /materials/2
Link to reproduction - Issues with a link to complete (but minimal) reproduction code will be addressed faster
https://github.com/ClemannD/next-13-test-repo
To Reproduce
Open a codespace, npm install && npm run build
I am running into problems as well. I am trying the top-down approach. Generate segments from the top down
categorySlug prop passed by parent segment's 'generateStaticParams' function
- the slug is coming back as undefined.
Looks like the same problem you are running into. The categorySlug
is not getting passed down to the child's generateStaticParams
.
For what it's worth, the Bottom Up approach does work.
If I change the ProductPage to have
export async function generateStaticParams(categorySlug: any) {
console.log("categorySlug", categorySlug);
return [
{
categorySlug: "food",
productId: "1",
},
{
categorySlug: "food",
productId: "2",
},
{
categorySlug: "materials",
productId: "1",
},
{
categorySlug: "materials",
productId: "2",
},
];
}
it generates as expected:
Route (app) Size First Load JS
┌ ○ / 0 B 0 B
├ λ /[categorySlug] 137 B 66.2 kB
└ ● /[categorySlug]/[productId] 139 B 66.2 kB
├ /food/1
├ /food/2
├ /materials/1
└ /materials/2
+ First Load JS shared by all 66.1 kB
├ chunks/17-adeb71b82e6fb442.js 63.8 kB
├ chunks/main-app-f7991bdf5a7d528d.js 200 B
└ chunks/webpack-d2d5d8dda7543a35.js 2.06 kB
Good to know! Will have to implement this. Thanks!
It also not work for catch all segments:
A required parameter (slug) was not provided as an array in generateStaticParams for /[...slug]
Return of generateStaticParams
is:
[ { slug: '/f' }, { slug: '/e' }, { slug: '/d' }, { slug: '/c' }, { slug: '/b' }, { slug: '/a' } ]
I am not sure if this is a bug or a feature.
It seems that only the output of the generateStaticParams
in [categorySlug]/layout.tsx
is forwarded as an argument to [categorySlug]/[productId]/page.tsx
.
The generateStaticParams
in [categorySlug]/page.tsx
will generate paths and override [categorySlug]/layout.tsx
only for that parent segment.
In case of your test repo, moving the generateStaticParams
from [categorySlug]/page.tsx
to [categorySlug]/layout.tsx
will generate this:
┌ ○ / 0 B 0 B
├ ● /[categorySlug] 160 B 69.4 kB
├ ├ /food
├ └ /materials
└ ● /[categorySlug]/[productId] 161 B 69.4 kB
├ /food/1
├ /food/2
├ /materials/1
└ /materials/2
+ First Load JS shared by all 69.3 kB
├ chunks/17-07b769614d6c9bc3.js 66.4 kB
├ chunks/main-app-f7991bdf5a7d528d.js 253 B
└ chunks/webpack-d2d5d8dda7543a35.js 2.63 kB
and if I also add the generateStaticParams
to [categorySlug]/page.tsx
:
export async function generateStaticParams() {
return [{
categorySlug: 'overriden',
}]
}
the output will be:
Route (app) Size First Load JS
┌ ○ / 0 B 0 B
├ ● /[categorySlug] 160 B 69.4 kB
├ └ /overriden
└ ● /[categorySlug]/[productId] 161 B 69.4 kB
├ /food/1
├ /food/2
├ /materials/1
└ /materials/2
+ First Load JS shared by all 69.3 kB
├ chunks/17-07b769614d6c9bc3.js 66.4 kB
├ chunks/main-app-f7991bdf5a7d528d.js 253 B
└ chunks/webpack-d2d5d8dda7543a35.js 2.63 kB
Is this a flawed implementation of the bottom up approach? When I console.log(params)
I am getting the params for every post.
export async function generateStaticParams() {
const paths = await getAllPostsSlugs();
function loopParams(posts: Post[]) {
const params: { courseSlug: string; sectionSlug: string; postSlug: string }[] = [];
posts.forEach((post) => {
const postSlug = post?.slug;
post?.sections?.forEach((section) => {
const sectionSlug = section?.slug;
section?.courses?.forEach((course) => {
const courseSlug = course?.slug;
params.push({ courseSlug, sectionSlug, postSlug });
});
});
});
return params;
}
return loopParams(paths);
}
Let's say I have a post 1 and 2.
If I visit /course/section/1
, I get:
{
courseSlug: 'course',
sectionSlug: 'section',
postSlug: '1'
}
{
courseSlug: 'course',
sectionSlug: 'section',
postSlug: '2'
}
Instead of just:
{
courseSlug: 'course',
sectionSlug: 'section',
postSlug: '1'
}
@ClemannD There is a typo in the documentation that suggest generateStaticParams
receives a params argument. It actually recieves an object with a params property.
https://github.com/ClemannD/next-13-test-repo/blob/main/app/%5BcategorySlug%5D/%5BproductId%5D/page.tsx#L10
this should be
export async function generateStaticParams({ params: { categorySlug }}: any) {
Additionally the reason no params are passed in for the given setup has to do with what Layouts and Pages are in scope for a given static generation pass.
For /app/[categorySlug]/[productId]/page.tsx
the only layout.js
in the ancestor path is the root /app/layout.js
and this does not define a generateStaticParams
nor could it because there are no params at that route level.
The reason the generateStaticParams
found in /app/[categorySlug]/page.tsx
aren't used is this page is not part of the layout hierarchy for /app/[categorySlug]/[productId]/page.tsx
.
If you created a /app/[categorySlug]/layout.tsx
and moved your generateStaticParams
from the page.tsx to it then params would be successfully passed into the deeper generateStaticParams
for productId.
You can use an empty Layout export default function ({ children }) { return children }
if you don't actually have any shared layout content to render.
However if you did not want to go with that approach you can just output every combination of categorySlug and productId from the generateStaticParams
you have already defined
I was able to reproduce the top down approach having issues: CodeSandbox or Repo.
Slightly related: I am looking for types for 3 things:
-
Some App-folder equivalent for:
const Page = ({ someProp }: InferGetStaticPropsType<typeof getStaticProps>
-
A
NextPage
replacement than works withnext build
.
Im sure its on your roadmap, but are they already in 13.1.7-canary.14
?
@ClemannD There is a typo in the documentation that suggest
generateStaticParams
receives a params argument. It actually recieves an object with a params property.https://github.com/ClemannD/next-13-test-repo/blob/main/app/%5BcategorySlug%5D/%5BproductId%5D/page.tsx#L10
this should be
export async function generateStaticParams({ params: { categorySlug }}: any) {
Additionally the reason no params are passed in for the given setup has to do with what Layouts and Pages are in scope for a given static generation pass.
For
/app/[categorySlug]/[productId]/page.tsx
the onlylayout.js
in the ancestor path is the root/app/layout.js
and this does not define agenerateStaticParams
nor could it because there are no params at that route level.The reason the
generateStaticParams
found in/app/[categorySlug]/page.tsx
aren't used is this page is not part of the layout hierarchy for/app/[categorySlug]/[productId]/page.tsx
.If you created a
/app/[categorySlug]/layout.tsx
and moved yourgenerateStaticParams
from the page.tsx to it then params would be successfully passed into the deepergenerateStaticParams
for productId.You can use an empty Layout
export default function ({ children }) { return children }
if you don't actually have any shared layout content to render.However if you did not want to go with that approach you can just output every combination of categorySlug and productId from the
generateStaticParams
you have already defined
This works, thank you! The parent generateStaticParams is returning a promise to the child level generateStaticParams, therefore an 'any' type will solve this problem.
Hi, this has been updated in v13.2.4-canary.7
of Next.js, please update and give it a try!
Hi, this has been updated in
v13.2.4-canary.7
of Next.js, please update and give it a try!
Tried this, but still after update, my params object in getStaticParams at app/lessons/[lessonId]/[sessionId]/page.tsx
is empty, even though I output slugs at app/lessons/[lessonId]/page.tsx
@arnelamo can you provide a minimal repo with this issue?
This closed issue has been automatically locked because it had no new activity for a month. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.