You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
When I changed the dynamic validation rules to be based on the existence of a static shell I removed an important protection for apps that have static metadata. Now that metadata is implicitly rendered within a Suspense boundary it is always opted into allowing dynamic. For dynamic and partially static pages this is fine because we are going to be generating a response per request anyways. But if you have a fully static page and then later accidentally make your metadata dynamic your page will deopt to partially static without any warning.
This change reintroduces the heuristic where if the only dynamic thing on the page is metadata the build errors. If there is at least one other dynamic thing on the page then dynamic metadata is allowed.
A similar change is not necessary for viewport because that is never rendered in a Suspense boundary and the only way to have dynamic viewports is to opt the entire app into dynamic with a Suspense boundary around your root layout
title: Cannot access Request information or uncached data in `generateMetadata()` in an otherwise entirely static route
3
+
---
4
+
5
+
## Why This Error Occurred
6
+
7
+
When `dynamicIO` is enabled, Next.js requires that `generateMetadata()` not depend on uncached data or Request data unless unless some other part of the page also has similar requirements. The reason for this is that while you normally control your intention for what is allowed to be dynamic by adding or removing Suspense boundaries in your Layout and Page components you are not in control of rendering metadata itself.
8
+
9
+
The heuristic Next.js uses to understand your intent with `generateMetadata()` is to look at the data requirements of the rest of the route. If other components have dependence on Request data and uncached data then we allow `generateMetadata()` to have similar data requirements. If the rest of your page has no dependence on this type of data we require that `generateMetadata()` also not have this type of data dependence.
10
+
11
+
## Possible Ways to Fix It
12
+
13
+
To fix this issue, you must first determine your goal for the affected route.
14
+
15
+
### Caching External Data
16
+
17
+
If your metadata does not depend on any request data then it may be possible for you to indicate the data is cacheable which would allow Next.js to include it in the prerender for this route. Consider using `"use cache"` to mark the function producing the external data as cacheable.
18
+
19
+
Before:
20
+
21
+
```jsx filename="app/.../page.tsx"
22
+
import { cms } from'./cms'
23
+
24
+
exportasyncfunctiongenerateMetadata() {
25
+
// This data lookup is not cached at the moment so
26
+
// Next.js will interpret this as needing to be rendered
27
+
// on every request.
28
+
const { title } =awaitcms.getPageData('/.../page')
29
+
return {
30
+
title,
31
+
}
32
+
}
33
+
34
+
asyncfunctiongetPageText() {
35
+
'use cache'
36
+
const { text } =awaitcms.getPageData('/.../page')
37
+
return text
38
+
}
39
+
40
+
exportdefaultasyncfunctionPage() {
41
+
// This text is cached so the main content of this route
42
+
// is prerenderable.
43
+
consttext=awaitgetPageText()
44
+
return<article>{text}</article>
45
+
}
46
+
```
47
+
48
+
After:
49
+
50
+
```jsx filename="app/.../page.tsx"
51
+
import { cms } from'./cms'
52
+
53
+
exportasyncfunctiongenerateMetadata() {
54
+
// By marking this function as cacheable, Next.js
55
+
// can now include it in the prerender for this route.
56
+
'use cache'
57
+
const { title } =awaitcms.getPageData('/.../page')
58
+
return {
59
+
title,
60
+
}
61
+
}
62
+
63
+
asyncfunctiongetPageText() {
64
+
'use cache'
65
+
const { text } =awaitcms.getPageData('/.../page')
66
+
return text
67
+
}
68
+
69
+
exportdefaultasyncfunctionPage() {
70
+
// This text is cached so the main content of this route
71
+
// is prerenderable.
72
+
consttext=awaitgetPageText()
73
+
return<article>{text}</article>
74
+
}
75
+
```
76
+
77
+
### If you must access Request Data or your external data is uncacheable
78
+
79
+
If your metadata requires request specific data or depends on external data which is not cacheable then Next.js will need to render this page dynamically on every request. However if you got this error the rest of your page is able to be completely static. This is generally pretty rare but if this is your actual constraint you can indicate to Next.js that the page should be allowed to be dynamic by rendering any other component that is dynamic. Since your route doesn't have any genuine dynamic requirements you might render an artificially dynamic component using `await connection()`. This is like telling Next.js that this component is never prerenderable and must be rendered on every user request.
80
+
81
+
Before:
82
+
83
+
```jsx filename="app/.../page.tsx"
84
+
import { cookies } from'next/headers'
85
+
86
+
exportasyncfunctiongenerateViewport() {
87
+
constcookieJar=awaitcookies()
88
+
return {
89
+
themeColor:cookieJar.get('theme-color'),
90
+
}
91
+
}
92
+
93
+
exportdefaultfunctionPage() {
94
+
return<main>This page is entirely static</main>
95
+
}
96
+
```
97
+
98
+
After:
99
+
100
+
```jsx filename="app/.../page.tsx"
101
+
import { Suspense } from'react'
102
+
import { cookies } from'next/headers'
103
+
import { connection } from'next/server'
104
+
105
+
exportasyncfunctiongenerateViewport() {
106
+
constcookieJar=awaitcookies()
107
+
return {
108
+
themeColor:cookieJar.get('theme-color'),
109
+
}
110
+
}
111
+
112
+
asyncfunctionDynamicMarker() {
113
+
// This component renders nothing but it will always
114
+
// be dynamic because it waits for an actual connection
Note: The reason to structure this DynamicMarker as a self-contained Suspense boundary is to avoid blocking the actual content of the page from being prerendered. When Partial Prerendering is enabled alongside dynamicIO the static shell will still contain all of the prerenderable content still and only the metadata will stream in dynamically.
// This prerender has a Suspense boundary above the body which
625
627
// effectively opts the page into allowing 100% dynamic rendering
628
+
dynamicValidation.hasAllowedDynamic=true
626
629
dynamicValidation.hasSuspenseAboveBody=true
627
630
return
628
631
}elseif(hasSuspenseRegex.test(componentStack)){
629
632
// this error had a Suspense boundary above it so we don't need to report it as a source
630
633
// of disallowed
634
+
dynamicValidation.hasAllowedDynamic=true
631
635
return
632
636
}else{
633
637
constmessage=`Route "${route}": A component accessed data, headers, params, searchParams, or a short-lived cache without a Suspense boundary nor a "use cache" above it. We don't have the exact line number added to error messages yet but you can see which component in the stack below. See more info: https://nextjs.org/docs/messages/next-prerender-missing-suspense`
@@ -646,54 +650,67 @@ function createErrorWithComponentStack(
646
650
returnerror
647
651
}
648
652
649
-
exportfunctionthrowIfDisallowedEmptyStaticShell(
653
+
exportfunctionthrowIfDisallowedDynamic(
650
654
route: string,
655
+
hasEmptyShell: boolean,
651
656
dynamicValidation: DynamicValidationState,
652
657
serverDynamic: DynamicTrackingState,
653
658
clientDynamic: DynamicTrackingState
654
659
): void{
655
-
if(dynamicValidation.hasSuspenseAboveBody){
656
-
// This route has opted into allowing fully dynamic rendering
657
-
// by including a Suspense boundary above the body. In this case
658
-
// a lack of a shell is not considered disallowed so we simply return
659
-
return
660
-
}
661
-
662
-
if(serverDynamic.syncDynamicErrorWithStack){
663
-
// There is no shell and the server did something sync dynamic likely
664
-
// leading to an early termination of the prerender before the shell
// If we got this far then the only other thing that could be blocking
690
-
// the root is dynamic Viewport. If this is dynamic then
691
-
// you need to opt into that by adding a Suspense boundary above the body
692
-
// to indicate your are ok with fully dynamic rendering.
693
-
if(dynamicValidation.hasDynamicViewport){
694
-
console.error(
695
-
`Route "${route}" has a \`generateViewport\` that depends on Request data (\`cookies()\`, etc...) or uncached external data (\`fetch(...)\`, etc...) without explicitly allowing fully dynamic rendering. See more info here: https://nextjs.org/docs/messages/next-prerender-dynamic-viewport`
696
-
)
697
-
thrownewStaticGenBailoutError()
692
+
thrownewStaticGenBailoutError()
693
+
}
694
+
695
+
// If we got this far then the only other thing that could be blocking
696
+
// the root is dynamic Viewport. If this is dynamic then
697
+
// you need to opt into that by adding a Suspense boundary above the body
698
+
// to indicate your are ok with fully dynamic rendering.
699
+
if(dynamicValidation.hasDynamicViewport){
700
+
console.error(
701
+
`Route "${route}" has a \`generateViewport\` that depends on Request data (\`cookies()\`, etc...) or uncached external data (\`fetch(...)\`, etc...) without explicitly allowing fully dynamic rendering. See more info here: https://nextjs.org/docs/messages/next-prerender-dynamic-viewport`
702
+
)
703
+
thrownewStaticGenBailoutError()
704
+
}
705
+
}else{
706
+
if(
707
+
dynamicValidation.hasAllowedDynamic===false&&
708
+
dynamicValidation.hasDynamicMetadata
709
+
){
710
+
console.error(
711
+
`Route "${route}" has a \`generateMetadata\` that depends on Request data (\`cookies()\`, etc...) or uncached external data (\`fetch(...)\`, etc...) when the rest of the route does not. See more info here: https://nextjs.org/docs/messages/next-prerender-dynamic-metadata`
0 commit comments