next-runtime icon indicating copy to clipboard operation
next-runtime copied to clipboard

[Bug]: next/image static images are not cached

Open jzxhuang opened this issue 1 year ago • 7 comments

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

image

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

image

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

  1. create a new next app with yarn create next-app --typescript
  2. render a statically imported image using next/image
  3. Deploy on Netlify
  4. 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.

jzxhuang avatar Jul 28 '22 11:07 jzxhuang

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!

jzxhuang avatar Jul 28 '22 11:07 jzxhuang

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.

ascorbic avatar Jul 28 '22 12:07 ascorbic

Thanks for the quick response!

Hmm, here's a screenshot of it taking 850ms. image

here's 2.5-5s (separate project): image

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) image

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!

jzxhuang avatar Jul 28 '22 14:07 jzxhuang

I am also experiencing this. doing a ahrefs.com scan on my site produces hundreds of 304 gateway timeouts

theskillwithin avatar Sep 07 '22 01:09 theskillwithin

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 :)

fool avatar Sep 22 '22 23:09 fool

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

nickytonline avatar Oct 12 '22 14:10 nickytonline

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 avatar Oct 12 '22 15:10 jzxhuang

@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 avatar Nov 07 '22 19:11 nickytonline

@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.

jzxhuang avatar Nov 10 '22 01:11 jzxhuang

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

rmellmer avatar Jun 06 '23 02:06 rmellmer

I am also still experiencing this issue.

adamknipfer avatar Jun 07 '23 21:06 adamknipfer

I have also this problem... i have a pro plan with Vercel, for this problem my counter is running fast...

Filippinifra avatar Jul 18 '23 23:07 Filippinifra