vercel / next.js

The React Framework
https://nextjs.org
MIT License
126.74k stars 26.94k forks source link

Build output doesn't warn when page is opted into dynamic rendering #63357

Open rijk opened 7 months ago

rijk commented 7 months ago

Link to the code that reproduces this issue

https://github.com/rijk/next-debug-fetch-cache

To Reproduce

  1. Update CACHE_BUSTER key in /app/constants.ts
  2. Deploy to Vercel
  3. Click One, Three or Four in the nav bar
  4. Observe 5s delay

Current vs. Expected behavior

Current behavior Navigating to e.g. One for the first time after building takes ~5s.

Expected behavior Navigating to e.g. One for the first time after building takes ms because the fetch is served from the build's Data Cache.

Background When the tree contains a component that uses e.g. cookies(), the route is opted into dynamic rendering at request time. To simulate this, I added a <Cart> component that accesses the cookies. Without this component, the whole app is fully static.

Every page contains a fetch that artificially delays rendering, but can and should be handled by the Data Cache. However, when deployed on Vercel I found that it is not using the values cached at build time, but rather does a new fetch the first time you access the page. Afterwards, the fetches are served from the cache (no 5s delay after refreshing).

Sidenote I'd also expect the routes with data cache to perform more closely to full route cache. Especially least when they can be prefetched. Navigating to Home is instant, navigating to One or Two takes about 200ms and shows the loading fallback.

Provide environment information

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 23.4.0: Wed Feb 21 21:51:37 PST 2024; root:xnu-10063.101.15~2/RELEASE_ARM64_T8112
Binaries:
  Node: 20.11.0
  npm: 10.2.4
  Yarn: 1.22.19
  pnpm: 8.15.4
Relevant Packages:
  next: 14.1.3
  eslint-config-next: N/A
  react: 18.2.0
  react-dom: 18.2.0
  typescript: N/A
Next.js Config:
  output: N/A

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

App Router, Data fetching (gS(S)P, getInitialProps)

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

Vercel (Deployed)

Additional context

This happens on both the normal and edge runtime.

rijk commented 7 months ago

Since filing this issue, I've learned that some of my assumptions were incorrect.

  1. Fetches done during build do not seed the Data Cache used in production. I intuitively assumed that was the case, but it isn't. It might be added in the future though.
  2. Dynamic pages cannot be prefetched. The full route prefetching after which navigation becomes instant, only happens for static pages.
  3. A Suspense boundary around cookies() does not prevent the rest of the tree from being rerendered. This will be possible in the future with Partial Prerendering, but that's still experimental and really not ready for production yet.

Point of criticism:


Here's the build output:

 ✓ Generating static pages (9/9) 
 ✓ Collecting build traces    
 ✓ Finalizing page optimization    

Route (app)                              Size     First Load JS
┌ ○ /                                    162 B          86.1 kB
├ ○ /_not-found                          162 B          86.1 kB
├ ● /dynamic/[dynamic]                   162 B          86.1 kB
├   ├ /dynamic/three
├   └ /dynamic/four
├ ○ /one                                 162 B          86.1 kB
└ ○ /two                                 162 B          86.1 kB
+ First Load JS shared by all            85.9 kB
  ├ chunks/533-a64464b27ba4c7cf.js       30.4 kB
  ├ chunks/7c09da68-8da926f2fb3b7d56.js  53.6 kB
  └ other shared chunks (total)          1.84 kB

○  (Static)  prerendered as static content
●  (SSG)     prerendered as static HTML (uses getStaticProps)

Note that everything looks good (static), and there is no mention that the pages will be "deopted" into dynamic mode during runtime. Let alone why. It would be very helpful if this would be detected and communicated here, something like:

[!] <Cart> component calls `cookies()` which will change this page to dynamic rendering