next 15 upgrade turned every fetch uncached and my pricing api bill went 16x Caching
Upgraded from 14.2.15 to 15.x over a weekend. No code changes to the route, no errors, build passed, lighthouse fine. Six days later the upstream vendor emails me because I went from about 5,760 calls a day to 96,000.
The page is app/p/[slug]/page.tsx, four await fetch() calls to a pricing API, and I had assumed for a year that fetch was cached by default and that my revalidate = 60 was doing something. Apparently not any more.
What is the actual current way to say "cache this for a minute"? And is there anything in the build output that would have told me before the invoice did?
@litercount_lou · 3h ago · 3 replies
The default flipped. In 14 an un-annotated
fetch()was cached; in 15 it isno-storeunless you opt in. Same for GET route handlers, which used to be static and now are not.Two ways to opt back in, and they compose:
And yes, the build output would have told you. The legend at the end of
next buildmarks each route static or dynamic. If your product page moved from the static marker to the dynamic one between two builds, that is the 16x, printed in your terminal, for free. Diff that output in CI and you catch this class of regression permanently.Reply
Report
@lowtech_lior · 3h ago
Confirmed, the route went dynamic in the build output and I never looked. Segment-level
revalidateput me back to 5,760/day. Feels bad that the only signal was a legend I had trained myself to scroll past.Reply
Report
@awkward_ash · 3h ago
Worth adding: if anything in that segment reads cookies or headers, the segment goes dynamic regardless of what you put on the fetch, and then your
revalidateis quietly meaningless. Check for acookies()call in a layout above the page before you conclude the fetch config is wrong.Reply
Report