next-runtime
next-runtime copied to clipboard
[Bug]: next/image static images are not cached
Summary
When using next/image
with Netlify edge functions, statically imported images are never cached. This results in the browser always making a request, which results in a 304 status code (asset unchanged). This is effectively making next/image
unusable, lack of caching is a severe degradation to app performance
See here:
- Open https://rainbow-haupia-6bb9be.netlify.app/ with devtools network tab open, filter for images
- on first load, the "react image" should have 200 status code. And be loaded in
webp
assuming your browser supports that format :D - On subsequent loads, the request is 304. I would expect 200 here with a browser cache hit. If you have multiple images on a page or hit a cold funciton boot, this request can take over 2s.
- Root cause appears to be that the response applies
cache-control: public, max-age=0, must-revalidate
header
Compare this to the same application deployed on Vercel: https://netlify-next-image-caching.vercel.app/
- Subsequent loads result in a browser-side cache hit.
- Header:
cache-control: public,max-age=31536000,immutable
- Next.js docs explicitly state that statically imported assets are hashed and cached forever. https://nextjs.org/docs/api-reference/next/image#minimum-cache-ttl
You can configure the Time to Live (TTL) in seconds for cached optimized images. In many cases, it's better to use a Static Image Import which will automatically hash the file contents and cache the image forever with a Cache-Control header of immutable
I also tried specifying minimumCacheTTL
, didn't appear to do anything. https://deploy-preview-1--rainbow-haupia-6bb9be.netlify.app/
Steps to reproduce
- create a new next app with
yarn create next-app --typescript
- render a statically imported image using
next/image
- Deploy on Netlify
- observe that the image is not cached
A link to a reproduction repository
https://github.com/jzxhuang/netlify-next-image-caching
Plugin version
4.13.1
More information about your build
- [ ] I am building using the CLI
- [X] I am building using file-based configuration (
netlify.toml
)
What OS are you using?
Mac OS
Your netlify.toml file
`netlify.toml`
# Paste content of your `netlify.toml` file here
[[plugins]]
package = "@netlify/plugin-nextjs"
Your public/_redirects file
`_redirects`
# Paste content of your `_redirects` file here
Your next.config.js
file
`next.config.js`
# Paste content of your `next.config.js` file here. Check there is no private info in there.
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
swcMinify: true,
}
module.exports = nextConfig
Builds logs (or link to your logs)
Build logs
# Paste logs here
https://app.netlify.com/sites/rainbow-haupia-6bb9be/deploys/62e26aa8c3966d7a427076d1
Function logs
Function logs
# Paste logs here
.next JSON files
generated .next JSON files
# Paste file contents here. Please check there isn't any private info in them
# You can either build locally, or download the deploy from Netlify by clicking the arrow next to the deploy time.
cc @ascorbic wanted to ping you on this one — would love to know if there's an easy fix or it would take some more time. We would love to start using next/image more, especially with the new edge function support, but this is a dealbreaker for us — seeing requests take >1.5s when the image can easily be cached
I'm looking back at our production app and it looks like this is not necessarily a regression with edge functions, the old lambda function implementation also appears to always set cache time to 0 and require a full network round trip to return a 304 status code 😢
Thanks!
Hi. Netlify uses must-revalidate by default because of atomic deploys: it ensures that assets from old deploys are never left around. It uses etags, so the 304 response should be very quick. The other reason we use that code is because /_next/image
uses content negotiation: your browser sends an Accept
header, and it returns the correct format according to what your browser supports. If there is a match then it should be very quick to return, because all it does is check the etag and return if it matches. If it's taking 1.5s after the first request then there's something wrong.
Thanks for the quick response!
Hmm, here's a screenshot of it taking 850ms.
here's 2.5-5s (separate project):
On a "better" case, we can see ~250-500ms, but this is still very noticeable in the UI (you see nothing in the UI) compared to cached response (0s)
Usually the slower requests are on the "first load of site after a while". I assume there is some startup time there (though edge function should be faster/always ready compared to lambda?).
But if you look closely at the second screenshot, you can see that the group of requests taking 5+s were sent after the first three images resolved, so maybe not "cold boot" related
I'm not sure I understand the comment on "atomic deploys" — Vercel uses atomic deploys too. For statically imported assets, I assume the idea is to generate hash from the content.
Looking at https://rainbow-haupia-6bb9be.netlify.app/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Freact.6faf514e.jpeg&w=2048&q=75
- I assume the hash is
6faf514e
? - I can on a different deploy preview, it has the same hash:
https://62e26aa8c3966d7a427076d1--rainbow-haupia-6bb9be.netlify.app/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Freact.6faf514e.jpeg&w=2048&q=75
- leads me to believe caching static imports forever should be safe. Not clear to me what happens for remote assets, haven't tried that out yet
And on Vercel:
-
https://netlify-next-image-caching.vercel.app/_next/image?url=/_next/static/media/react.6faf514e.jpeg&w=2048&q=75
-
https://netlify-next-image-caching-2olg8tiu4-jzxhuang.vercel.app/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Freact.6faf514e.jpeg&w=2048&q=75
Thanks!
I am also experiencing this. doing a ahrefs.com scan on my site produces hundreds of 304 gateway timeouts
something to be aware of - ahrefs.com is treated as a prerendering-required user agent, so if you have prerendering enabled for your site, it will load differently when requested with some User-Agent settings such as ahrefsbot
. This article has more details about how to debug prerendering failures, if this sounds like your situation, @theskillwithin : https://answers.netlify.com/t/support-guide-understanding-and-debugging-prerendering/150 - it could be something that you could fix if you try :)
If a request ID could be provided for the slow requests or even better a HAR, that will allow us to diagnose this better. If you're not sure how to generate a HAR, see https://support.google.com/admanager/answer/10358597
If a request ID could be provided for the slow requests or even better a HAR, that will allow us to diagnose this better. If you're not sure how to generate a HAR, see https://support.google.com/admanager/answer/10358597
Thanks for the reply @nickytonline. This should be quite easy to reproduce — any next.js site with the edge middleware and using next/image
to render a statically imported image. Then check in devtools that the image is never cached?
This is outlined in clear repro steps with a repo isolating the issue... unless this has been fixed in a recent version of Netlify's runtime that I haven't had the chance to try yet. I've included deploy previews to compare Netlify vs Vercel of the same code too
If you need any more clarification let me know, but I want to reiterate that this makes using next/image
difficult/impossible for any page that renders a large number of static images. Lack of client-side caching + large number of network requests is not desirable
@jzxhuang just following up on this. We've had several bug fixes over the past 26 days. Mind trying out the the latest version of the Next.js Runtime or if you don't have it in your package.json, when you do a git push for deploy, it'll pull the latest version.
Let us know if the above is still an issue after trying that. Thanks!
@nickytonline I upgraded to latest (4.28.6) on https://github.com/jzxhuang/netlify-next-image-caching/pull/2, not seeing the desired behaviour.
You can compare cache-control
header on Vercel vs Netlify for the React logo image. public, max-age=0, must-revalidate
is served by Netlify, public,max-age=31536000,immutable
by Vercel.
I guess it's no longer returning a 304, re-reading my initial report that was an issue. But it's still not cached. Imagine we have even 10 static images on a site, using next/image
is significantly worse than just using a normal img
tag — it really makes next/image
very difficult to use in many situations.
Is this really fixed? I’m still experiencing the same issue that @jzxhuang was facing in https://github.com/netlify/next-runtime/issues/1491#issuecomment-1309651337
I am also still experiencing this issue.
I have also this problem... i have a pro plan with Vercel, for this problem my counter is running fast...