payloadcms/payload

Cannot build with dynamicIO enabled.

Opened this issue · 7 comments

Describe the Bug

When enabling the experimental dynamicIO feature, the following error occurs during build:

Error occurred prerendering page "/admin/[[...segments]]". Read more: https://nextjs.org/docs/messages/prerender-error
Error: Route "/admin/[[...segments]]" has a `generateMetadata` that depends on Request data (`cookies()`, etc...) or external data (`fetch(...)`, etc...) but the rest of the route was static or only used cached data (`"use cache"`). If you expected this route to be prerenderable update your `generateMetadata` to not use Request data and only use cached external data. Otherwise, add `await connection()` somewhere within this route to indicate explicitly it should not be prerendered.
Export encountered an error on /(payload)/admin/[[...segments]]/page: /admin/[[...segments]], exiting the build.

While this is experimental, the documentation for the new 'use cache' directive states that it aims to replace the unstable_cache API.

Given that we will be using the payload client directly in Server Actions, we will need a way to cache those actions with tags (for revalidateTag). React's cache doesn't support tagging for manual revalidation, which leaves only Next's unstable_cache and 'use cache' with cacheTag as options.

Link to the code that reproduces this issue

blank

Reproduction Steps

Enable experimental dynamicIO feature.

Build.

Which area(s) are affected? (Select all that apply)

area: core

Environment Info

Payload: beta-119
Node: 20
Next.js: 15.0.2-canary.9

Oh man thanks for the report here. This is a real can of worms. You're right, Next.js throws an error here but I am not sure why.

Reading the error, it basically says that generateMetadata depends on request data, but the rest of the page does not.

However, this is 100% untrue as our Page component definitely uses headers() which would opt the whole route into not being able to be prerendered.

I think this might be a Next.js canary thing...

I also just updated our /admin/[[...segments]] page template as the error message instructed, by adding await connection() but this did not fix the build error.

// import { connection } from 'next/server'

const Page = async ({ params, searchParams }: Args) => {
  await connection()
  return RootPage({ config, params, searchParams, importMap })
}

Gonna have to look into this one a bit further and get back to you!

got the same error and the
await connection() doesn't help

import React, { FC } from 'react'
import { Metadata } from 'next'
import { connection } from 'next/server'

type DashboardProps = {
  searchParams: Promise<{ [key: string]: string | string[] | undefined }>
}

export const generateMetadata = async (
  props: DashboardProps
): Promise<Metadata> => {
  const searchParams = await props.searchParams
  const { page: currentPage } = searchParams

  return {
    description: `${currentPage}`,
  }
}

const Page: FC<DashboardProps> = async () => {
  await connection()

  return <div>Page</div>
}

export default Page

and the log:

Next.js 15.0.2-canary.11
   - Environments: .env.local, .env
   - Experiments (use with caution):
     · turbo
     · dynamicIO

   Creating an optimized production build ...
 ✓ Compiled successfully
 ✓ Linting and checking validity of types    
 ✓ Collecting page data

Error occurred prerendering page "/". Read more: https://nextjs.org/docs/messages/prerender-error
Error: Route "/" has a `generateMetadata` that depends on Request data (`cookies()`, etc...) or external data (`fetch(...)`, etc...) but the rest of the route was static or only used cached data (`"use cache"`). If you expected this route to be prerenderable update your `generateMetadata` to not use Request data and only use cached external data. Otherwise, add `await connection()` somewhere within this route to indicate explicitly it should not be prerendered.
Export encountered an error on /(public)/page: /, exiting the build.
 ⨯ Static worker exited with code: 1 and signal: null

node -v
v22.11.0

Instead of using await connection() at the page level, wrapping the layout with <Suspense> solved the problem for me.

app/(payload)/layout.tsx

import configPromise from "@payload-config";
import "@payloadcms/next/css";
import { RootLayout } from "@payloadcms/next/layouts";
import React, { Suspense } from "react";

import "./custom.scss";
import { importMap } from "./admin/importMap";

type Args = {
  children: React.ReactNode;
};

const Layout = ({ children }: Args) => (
  <Suspense>
    <RootLayout importMap={importMap} config={configPromise}>
      {children}
    </RootLayout>
  </Suspense>
);

export default Layout;

See also the Next.js blog entry: Our Journey with Caching - Dynamic

I can't judge whether that's technically correct. My understanding of Next.js isn't deep enough for that.

I think wrapping the layout this high up would have a negative impact on the /api routes as well, but I'm not certain about that. I did also try wrapping the app/(payload)/admin/[[segments]]/page.tsx RootPage with Suspense, as well as creating a loading.tsx file (also generates a Suspense boundary), and finally creating a app/(payload)/admin/layout.tsx wrapping the children prop with Suspense. None of those worked.

It is interesting though that wrapping the root payload layout with Suspense works. This seems to indicate there's something in RootLayout causing the issue rather than RootPage as the error suggests.

This error said:
throw new CanaryOnlyError('experimental.dynamicIO');
^

CanaryOnlyError: The experimental feature "experimental.dynamicIO" can only be enabled when using the latest canary version of Next.js.

just upgrade npm i next@canary

@pedroaparecido As listed originally in my issue post, canary is being used. When you install canary, and enable dynamicIO, this issue occurs. While this is a canary-only option now, it has been stated by the Next.JS team to be the expected successor to the unstable_cache function. This would make the new use cache directive the only first-party way to cache server actions that use the local payload API with the ability to tag each cache and manually invalidate them.

Just bumped into this issue as well trying to deploy to Vercel.
@Figumari solution worked but I am getting the exact same error on my dynamic routes now too, outside of admin/.