vercel / vercel/next.js

Docs: should clarify whether we can call redirect within generateMetadata

Open
#80,879 3 comments 2 reactions 1 assignee View on GitHub

@icyJoseph is already working on this.

Since Jun 25, 2025.

Improvement
Dominant language
JavaScript
Stars
142k
Forks
32.4k
Avg merge
2d 14h
Merged PRs (30d)
351

Description

What is the documentation issue?

It is unclear whether redirect, permanentRedirect, and notFound can be called from generateMetadata.

  • permanentRedirect: “permanentRedirect can be used in Server Components, Client Components, Route Handlers, and Server Actions”. No mention of generateMetadata so it sounds like it is prohibited.
  • redirect: “redirect can be used in Server Components, Route Handlers, and Server Actions.” No mention of generateMetadata so it sounds like it is prohibited. Also does not include Client Components although there is an example of client component below.
  • notFound just says to use it “within a route segment”; does not say specifically where.
  • generateMetadata says “The redirect() and notFound() Next.js methods can also be used inside generateMetadata.”, contradicting the redirect documentation. Also no mention of permanentRedirect.

I tested a basic page.tsx:

import { redirect } from "next/navigation";
import type { Metadata } from 'next';

export async function generateMetadata(): Promise<Metadata> {
    await new Promise(resolve => setTimeout(resolve, 1000))
    redirect('/generate-metadata-redirect');
}

export default async function Page() {
    await new Promise(resolve => setTimeout(resolve, 2000))
    redirect('/server-component-redirect');
}

In nextjs 14, it seems that the first redirect wins, and the redirect from generateMetadata is used. On the other hand, in nextjs 15, it seems that the Server Component redirect always wins.

On the other hand, if the Server Component does not throw a redirect:

import { redirect } from "next/navigation";
import type { Metadata } from 'next';

export async function generateMetadata(): Promise<Metadata> {
    await new Promise(resolve => setTimeout(resolve, 1000))
    redirect('/generate-metadata-redirect');
}

export default async function Page() {
    await new Promise(resolve => setTimeout(resolve, 2000))
    return (
        <div>
            <h1>My Path Page</h1>
        </div>
    );
}

Then nextjs 14 returns the 307 redirect from generateMetadata, but nextjs 15 returns status code 200 but then performs a redirect using javascript. The source code of the page contains the stack trace:

<template data-dgst="NEXT_REDIRECT;replace;/generate-metadata-redirect;307;" data-msg="Switched to client rendering because the server rendering errored:

NEXT_REDIRECT" data-stck="Switched to client rendering because the server rendering errored:

Error: NEXT_REDIRECT
    at Module.generateMetadata (webpack-internal:///(rsc)/./app/mypath/page.tsx:13:62)
    at resolveErrorDev (/Users/yonran/Documents/tutorials/nextjs-redirects/nextjs-dashboard/node_modules/.pnpm/next@15.3.4_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:186:44590)
    at getOutlinedModel (/Users/yonran/Documents/tutorials/nextjs-redirects/nextjs-dashboard/node_modules/.pnpm/next@15.3.4_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:186:32934)
    at /Users/yonran/Documents/tutorials/nextjs-redirects/nextjs-dashboard/node_modules/.pnpm/next@15.3.4_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:186:36809
    at ResponseInstance._fromJSON (/Users/yonran/Documents/tutorials/nextjs-redirects/nextjs-dashboard/node_modules/.pnpm/next@15.3.4_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:186:37621)
    at JSON.parse (&lt;anonymous&gt;)
    at initializeModelChunk (/Users/yonran/Documents/tutorials/nextjs-redirects/nextjs-dashboard/node_modules/.pnpm/next@15.3.4_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:186:25635)
    at resolveModelChunk (/Users/yonran/Documents/tutorials/nextjs-redirects/nextjs-dashboard/node_modules/.pnpm/next@15.3.4_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:186:25007)
    at /Users/yonran/Documents/tutorials/nextjs-redirects/nextjs-dashboard/node_modules/.pnpm/next@15.3.4_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:186:57533
    at /Users/yonran/Documents/tutorials/nextjs-redirects/nextjs-dashboard/node_modules/.pnpm/next@15.3.4_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:186:57638
    at progress (/Users/yonran/Documents/tutorials/nextjs-redirects/nextjs-dashboard/node_modules/.pnpm/next@15.3.4_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:186:57690)" data-cstck="
    at MetadataOutlet (webpack-internal:///(ssr)/./node_modules/.pnpm/next@15.3.4_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/next/dist/client/components/metadata/async-metadata.js:24:11)
    at Suspense (&lt;anonymous&gt;)
    at AsyncMetadataOutlet (webpack-internal:///(ssr)/./node_modules/.pnpm/next@15.3.4_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/next/dist/client/components/metadata/async-metadata.js:38:11)
    at StreamingMetadataOutlet [Server] (&lt;anonymous&gt;)
    at __next_outlet_boundary__ (webpack-internal:///(ssr)/./node_modules/.pnpm/next@15.3.4_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/next/dist/client/components/metadata/metadata-boundary.js:36:15)
    at InnerLayoutRouter (webpack-internal:///(ssr)/./node_modules/.pnpm/next@15.3.4_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/next/dist/client/components/layout-router.js:243:11)
    at RedirectErrorBoundary (webpack-internal:///(ssr)/./node_modules/.pnpm/next@15.3.4_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/next/dist/client/components/redirect-boundary.js:74:9)
    at RedirectBoundary (webpack-internal:///(ssr)/./node_modules/.pnpm/next@15.3.4_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/next/dist/client/components/redirect-boundary.js:82:11)
    at HTTPAccessFallbackBoundary (webpack-internal:///(ssr)/./node_modules/.pnpm/next@15.3.4_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/next/dist/client/components/http-access-fallback/error-boundary.js:96:11)
    at LoadingBoundary (webpack-internal:///(ssr)/./node_modules/.pnpm/next@15.3.4_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/next/dist/client/components/layout-router.js:324:11)
    at ErrorBoundary (webpack-internal:///(ssr)/./node_modules/.pnpm/next@15.3.4_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/next/dist/client/components/error-boundary.js:175:11)
    at InnerScrollAndFocusHandler (webpack-internal:///(ssr)/./node_modules/.pnpm/next@15.3.4_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/next/dist/client/components/layout-router.js:140:9)
    at ScrollAndFocusHandler (webpack-internal:///(ssr)/./node_modules/.pnpm/next@15.3.4_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/next/dist/client/components/layout-router.js:225:11)
    at RenderFromTemplateContext (webpack-internal:///(ssr)/./node_modules/.pnpm/next@15.3.4_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/next/dist/client/components/render-from-template-context.js:16:44)
    at OuterLayoutRouter (webpack-internal:///(ssr)/./node_modules/.pnpm/next@15.3.4_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/next/dist/client/components/layout-router.js:360:11)
    at InnerLayoutRouter (webpack-internal:///(ssr)/./node_modules/.pnpm/next@15.3.4_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/next/dist/client/components/layout-router.js:243:11)
    at RedirectErrorBoundary (webpack-internal:///(ssr)/./node_modules/.pnpm/next@15.3.4_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/next/dist/client/components/redirect-boundary.js:74:9)
    at RedirectBoundary (webpack-internal:///(ssr)/./node_modules/.pnpm/next@15.3.4_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/next/dist/client/components/redirect-boundary.js:82:11)
    at HTTPAccessFallbackErrorBoundary (webpack-internal:///(ssr)/./node_modules/.pnpm/next@15.3.4_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/next/dist/client/components/http-access-fallback/error-boundary.js:88:9)
    at HTTPAccessFallbackBoundary (webpack-internal:///(ssr)/./node_modules/.pnpm/next@15.3.4_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/next/dist/client/components/http-access-fallback/error-boundary.js:96:11)
    at LoadingBoundary (webpack-internal:///(ssr)/./node_modules/.pnpm/next@15.3.4_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/next/dist/client/components/layout-router.js:324:11)
    at ErrorBoundary (webpack-internal:///(ssr)/./node_modules/.pnpm/next@15.3.4_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/next/dist/client/components/error-boundary.js:175:11)
    at InnerScrollAndFocusHandler (webpack-internal:///(ssr)/./node_modules/.pnpm/next@15.3.4_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/next/dist/client/components/layout-router.js:140:9)
    at ScrollAndFocusHandler (webpack-internal:///(ssr)/./node_modules/.pnpm/next@15.3.4_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/next/dist/client/components/layout-router.js:225:11)
    at RenderFromTemplateContext (webpack-internal:///(ssr)/./node_modules/.pnpm/next@15.3.4_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/next/dist/client/components/render-from-template-context.js:16:44)
    at OuterLayoutRouter (webpack-internal:///(ssr)/./node_modules/.pnpm/next@15.3.4_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/next/dist/client/components/layout-router.js:360:11)
    at body (&lt;anonymous&gt;)
    at html (&lt;anonymous&gt;)
    at RootLayout [Server] (&lt;anonymous&gt;)
    at RedirectErrorBoundary (/Users/yonran/Documents/tutorials/nextjs-redirects/nextjs-dashboard/node_modules/.pnpm/next@15.3.4_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:248:4631)
    at RedirectBoundary (/Users/yonran/Documents/tutorials/nextjs-redirects/nextjs-dashboard/node_modules/.pnpm/next@15.3.4_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:248:4727)
    at AppDevOverlayErrorBoundary (/Users/yonran/Documents/tutorials/nextjs-redirects/nextjs-dashboard/node_modules/.pnpm/next@15.3.4_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:274:11584)
    at AppDevOverlay (/Users/yonran/Documents/tutorials/nextjs-redirects/nextjs-dashboard/node_modules/.pnpm/next@15.3.4_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:274:215362)
    at HotReload (/Users/yonran/Documents/tutorials/nextjs-redirects/nextjs-dashboard/node_modules/.pnpm/next@15.3.4_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:274:219791)
    at Router (/Users/yonran/Documents/tutorials/nextjs-redirects/nextjs-dashboard/node_modules/.pnpm/next@15.3.4_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:248:7783)
    at ErrorBoundaryHandler (/Users/yonran/Documents/tutorials/nextjs-redirects/nextjs-dashboard/node_modules/.pnpm/next@15.3.4_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:254:2994)
    at ErrorBoundary (/Users/yonran/Documents/tutorials/nextjs-redirects/nextjs-dashboard/node_modules/.pnpm/next@15.3.4_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:254:4291)
    at AppRouter (/Users/yonran/Documents/tutorials/nextjs-redirects/nextjs-dashboard/node_modules/.pnpm/next@15.3.4_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:248:14186)
    at ServerInsertedHTMLProvider (/Users/yonran/Documents/tutorials/nextjs-redirects/nextjs-dashboard/node_modules/.pnpm/next@15.3.4_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:418:461)
    at ServerInsertedMetadataProvider (/Users/yonran/Documents/tutorials/nextjs-redirects/nextjs-dashboard/node_modules/.pnpm/next@15.3.4_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:422:2243)
    at App (/Users/yonran/Documents/tutorials/nextjs-redirects/nextjs-dashboard/node_modules/.pnpm/next@15.3.4_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:422:12743)"></template><!--/$-->

I think the documentation should be clarified to say whether redirect, permanentRedirect, and notFound can or should be called from generateMetadata.

Is there any context that might help us understand?

No context.

Does the docs page already exist? Please link to it.

https://nextjs.org/docs/app/api-reference/functions/redirect

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.