Repository: vercel/next.js
Pull request: [dynamicIO] log dynamic validation errors consistently in dev

In dev when dynamicIO is enabled we perform a validating prerender to look for code that would be a build error in `next build`. However due to the construction of the earlier implementation of this code some validation messages were being suppressed because they were conveyed as a thrown error rather than through console.error. This change updates the mechanism by which we raise these issues in dev so they are consistently messaged.

stacked on #78574 

Changed files:
filename: packages/next/src/server/app-render/dynamic-rendering.ts
patch: @@ -691,8 +691,9 @@ export function throwIfDisallowedEmptyStaticShell(
   // you need to opt into that by adding a Suspense boundary above the body
   // to indicate your are ok with fully dynamic rendering.
   if (dynamicValidation.hasDynamicViewport) {
-    throw new StaticGenBailoutError(
+    console.error(
       `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`
     )
+    throw new StaticGenBailoutError()
   }
 }

filename: test/e2e/app-dir/dynamic-io-errors/dynamic-io-errors.test.ts
patch: @@ -257,10 +257,10 @@ function runTests(options: { withMinification: boolean }) {
         }
         const expectError = createExpectError(next.cliOutput)
 
-        expectError('Error occurred prerendering page "/"')
         expectError(
           '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'
         )
+        expectError('Error occurred prerendering page "/"')
       })
     })
 
@@ -299,10 +299,10 @@ function runTests(options: { withMinification: boolean }) {
         }
         const expectError = createExpectError(next.cliOutput)
 
-        expectError('Error occurred prerendering page "/"')
         expectError(
           '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'
         )
+        expectError('Error occurred prerendering page "/"')
       })
     })
 

filename: test/e2e/app-dir/dynamic-io-errors/fixtures/dynamic-metadata-dynamic-route/app/page.tsx
patch: @@ -9,10 +9,9 @@ export default async function Page() {
   return (
     <>
       <p>
-        This page is static except for generateMetadata which does some IO. This
-        is a build error because metadata is not wrapped in a Suspense boundary.
-        We expect that if you intended for your metadata to be dynamic you will
-        ensure your page is dynamic too
+        This page has dynamic content and generateMetadata is also dynamic.
+        generateMetadata being dynamic in this context is fine because it isn't
+        the only reason the page is dynamic.
       </p>
       <Suspense fallback={<Fallback />}>
         <Dynamic />