Files
Hendrik LiebauandGitHub 4f69336336 Support accessing root params in generateStaticParams (#91189)
A new `GenerateStaticParamsStore` work unit store type is now provided during `generateStaticParams` execution. This enables root param getters (`import { lang } from 'next/root-params'`) to be called inside `generateStaticParams`, allowing shared helpers that internally access root params via the special import to be used in both Server Components and `generateStaticParams` without manually threading params.

Each `generateStaticParams` call now runs within a `workUnitAsyncStorage.run()` context carrying a `GenerateStaticParamsStore` with the correct `rootParams` (extracted from `parentParams` using the already-available `rootParamKeys`). The store extends `CommonWorkUnitStore`, providing `phase` and `implicitTags` which are not strictly necessary but convenient to keep call sites simple.

Request-time APIs (`headers()`, `cookies()`, `connection()`, `draftMode()`) now throw specific errors when called inside `generateStaticParams` instead of the previous generic "called outside a request scope" message. Framework-internal functions like `createSearchParamsFromClient` and `createParamsFromClient` throw `InvariantError` since they should never be reached in this context.

This change also unblocks a follow-up PR that removes `| undefined` from `PublicCacheContext.outerWorkUnitStore` in the use cache wrapper, since `"use cache"` is already supported inside `generateStaticParams` today but previously ran without a `WorkUnitStore`. With this store in place, requiring a `WorkUnitStore` in `"use cache"` won't break that existing usage.
2026-03-16 15:44:53 +01:00

53 lines
2.0 KiB
TypeScript

import { nextTestSetup } from 'e2e-utils'
import cheerio from 'cheerio'
import { join } from 'path'
import { getCacheHeader } from 'next-test-utils'
describe('app-root-param-getters - generateStaticParams', () => {
const { next } = nextTestSetup({
files: join(__dirname, 'fixtures', 'generate-static-params'),
})
it('should be statically prerenderable', async () => {
const params = { lang: 'en', locale: 'us' }
const response = await next.fetch(`/${params.lang}/${params.locale}`)
expect(response.status).toBe(200)
expect(getCacheHeader(response)).toBeOneOf(['HIT', 'PRERENDER'])
const $ = cheerio.load(await response.text())
expect($('p').text()).toBe(`hello world ${JSON.stringify(params)}`)
})
it('should be part of the static shell', async () => {
const params = { lang: 'en', locale: 'us' }
const browser = await next.browser(
`/${params.lang}/${params.locale}/other/1`,
{
// prevent streaming (dynamic) content from being inserted into the DOM
disableJavaScript: true,
}
)
expect(await browser.elementByCss('main > p#root-params').text()).toBe(
JSON.stringify(params)
)
})
it('should allow reading root params that were not prerendered', async () => {
const params = { lang: 'sth', locale: 'else' }
const $ = await next.render$(`/${params.lang}/${params.locale}`)
expect($('p').text()).toBe(`hello world ${JSON.stringify(params)}`)
})
it('should allow reading root params inside generateStaticParams', async () => {
// The [slug] segment's generateStaticParams uses `lang()` to produce
// slugs like "en-post". If root params are available during
// generateStaticParams, this page should be statically prerenderable.
const response = await next.fetch('/en/us/other/en-post')
expect(response.status).toBe(200)
const $ = cheerio.load(await response.text())
expect($('#root-params').text()).toBe(
JSON.stringify({ lang: 'en', locale: 'us' })
)
expect($('#dynamic-params').text()).toBe('en-post')
})
})