How to Fix: Type ‘Props’ does not satisfy the constraint ‘PageProps’ (generateMetadata)
The Type ‘Props’ does not satisfy the constraint ‘PageProps’ error in generateMetadata usually means your route props no longer match what the current Next.js App Router type system expects. In practice, this often appears after a framework upgrade, especially when params and searchParams are typed manually in page files, layouts, or metadata functions.
Understanding the Root Cause
This bug happens because generateMetadata is a framework-controlled function with a strict signature. In newer Next.js versions, the App Router validates that the first argument passed to generateMetadata conforms to the framework’s internal PageProps contract for that route.
If your code defines a custom type like this:
type Props = {
params: { slug: string }
}
and then uses it here:
export async function generateMetadata({ params }: Props) {
// ...
}
TypeScript may fail because your Props type does not exactly satisfy the framework constraint being applied during type checking. This is especially common when:
- params or searchParams are missing from the expected shape
- The route is dynamic, but the prop type is too narrow or manually declared incorrectly
- The project uses generated Next.js route types, and local types drift out of sync
- A recent Next.js update changed how PageProps is inferred for page.tsx, layout.tsx, or generateMetadata
The key idea is simple: do not force a custom props type onto generateMetadata unless it exactly matches the route contract. Let Next.js infer the shape where possible, or define the props using the route-compatible structure the framework expects.
Step-by-Step Solution
The safest fix is to standardize the props used by your page and metadata functions so they match the route segment exactly.
1. Find every failing page, layout, or metadata export
Run your type check command and inspect files that contain generateMetadata:
pnpm check-types
# or
npm run check-types
# or
yarn check-types
2. Replace overly custom Props definitions
If you currently have something like this:
type Props = {
params: {
slug: string
}
}
export async function generateMetadata({ params }: Props) {
return {
title: params.slug,
}
}
export default function Page({ params }: Props) {
return <div>{params.slug}</div>
}
update it so the props shape matches the route more explicitly and consistently.
For a route like app/blog/[slug]/page.tsx, use:
type PageProps = {
params: {
slug: string
}
searchParams?: {
[key: string]: string | string[] | undefined
}
}
export async function generateMetadata({ params }: PageProps) {
return {
title: params.slug,
}
}
export default function Page({ params }: PageProps) {
return <div>{params.slug}</div>
}
3. If your Next.js version expects async params, use Promise-based props
In some setups, especially after newer App Router typing changes, params and searchParams may be typed as asynchronous values in generated type validation. In that case, use this form instead:
type PageProps = {
params: Promise<{
slug: string
}>
searchParams?: Promise<{
[key: string]: string | string[] | undefined
}>
}
export async function generateMetadata({ params }: PageProps) {
const { slug } = await params
return {
title: slug,
}
}
export default async function Page({ params }: PageProps) {
const { slug } = await params
return <div>{slug}</div>
}
If your type errors mention missing Promise methods like then, catch, or finally, this is a strong sign your local props type is out of sync with the current generated route expectation.
4. Match the route segment exactly
Your prop type must reflect the real folder structure:
- app/blog/[slug]/page.tsx – { slug: string }
- app/docs/[…parts]/page.tsx – { parts: string[] }
- app/shop/[[…category]]/page.tsx – { category?: string[] }
Examples:
type CatchAllProps = {
params: {
parts: string[]
}
}
type OptionalCatchAllProps = {
params: {
category?: string[]
}
}
5. Keep page and metadata signatures aligned
A frequent source of errors is using one prop type in the page component and a different one in generateMetadata. Reuse the same route-compatible type in both places.
type PageProps = {
params: {
slug: string
}
}
export async function generateMetadata({ params }: PageProps) {
return { title: params.slug }
}
export default function Page({ params }: PageProps) {
return <main>{params.slug}</main>
}
6. Regenerate types and retry
After updating the signatures, clear stale build artifacts and rerun checks:
rm -rf .next
npm run check-types
If your project uses a separate build or validation pipeline, run that too.
Working Examples
Example A: Standard dynamic route
type PageProps = {
params: {
slug: string
}
}
export async function generateMetadata({ params }: PageProps) {
return {
title: `Post: ${params.slug}`,
}
}
export default function Page({ params }: PageProps) {
return <article>{params.slug}</article>
}
Example B: Async params-compatible version
type PageProps = {
params: Promise<{
slug: string
}>
}
export async function generateMetadata({ params }: PageProps) {
const { slug } = await params
return {
title: `Post: ${slug}`,
}
}
export default async function Page({ params }: PageProps) {
const { slug } = await params
return <article>{slug}</article>
}
Example C: Dynamic route with search params
type PageProps = {
params: {
slug: string
}
searchParams?: {
preview?: string
}
}
export async function generateMetadata({ params, searchParams }: PageProps) {
return {
title: searchParams?.preview ? `Preview: ${params.slug}` : params.slug,
}
}
Common Edge Cases
- Catch-all routes typed as string instead of string[]
For folders like […slug], the value is an array, not a single string. - Optional catch-all routes missing undefined
For folders like [[…slug]], the param may be absent, so the type should allow undefined. - generateMetadata typed differently from the page component
Even if one compiles, the route validation can still fail during global type checks. - Stale .next generated types
After changing route signatures or upgrading Next.js, old generated types can continue causing false mismatches until you delete .next. - searchParams shape too strict
If you manually type every query parameter but Next expects a broader index signature, your custom props can become incompatible. - Framework upgrade changed route typing behavior
If the issue appeared suddenly after updating Next.js, compare your version’s App Router typing expectations and update route props accordingly.
FAQ
Why does this error appear only in generateMetadata and not always in the page component?
generateMetadata is validated against a stricter framework signature. Your page component may appear valid in isolation, while metadata exports are checked against generated route contracts that expose mismatches more aggressively.
Should I keep using a custom Props type?
Yes, but only if it matches the route exactly. A reusable local type is fine when it correctly models params and searchParams for that specific file. The problem is not custom typing itself; the problem is a custom type that no longer satisfies the framework’s PageProps constraint.
How do I know whether params should be synchronous or Promise-based?
Check the actual type error. If TypeScript says your object is missing Promise methods such as then or catch, your current Next.js setup is expecting async-shaped route props in generated validation. In that case, update both the page and generateMetadata signatures consistently.
The fix comes down to one rule: make your route props reflect the exact App Router contract for that segment, then reuse the same type in both the page and generateMetadata. Once those signatures are aligned, the Type ‘Props’ does not satisfy the constraint ‘PageProps’ error disappears during check-types.