Search code examples
reactjsdockernext.jsnetwork-programming

next/image not consistently loading when used within a docker container?


First some context. I have 2 docker containers running for development: api & frontend:

  • The api container consists of a Strapi back-end that also hosts all our images.
  • The frontend container consists of our NextJS "front-end".

Since next/image will automatically optimize the images on the server-side, we pass the following base url for our images: http://host.docker.internal:1337/uploads/*filenamehere.ext*

And this works fine for 80% of the images. But sometimes it will not use the NextJS image optimization for some reason, and will then try to access the image directly.

Like you can see here: Image of 2 successful optimized image requests and one unoptimized image

The problem here is that it will try to access the url http://host.docker.internal:1337/uploads/carbon_..etc.svg directly from the client-side, which is not possible...

What's a good solution for this? I want to keep using Next's image optimization.

The easiest would probably be to access the "api" container from my host machine, using: 127.0.0.1:1337. But is there not any other way?

I would like to prevent the usage of host networking.


Solution

  • Cause

    The requests which are not functioning are for SVGs. NextJS doesn't need to optimize SVGs since they are a vector format which is represented as text.

    That means it does nothing with them by default, and therefore the URL to the "raw" image location is ultimately used directly by the HTML <img> tag. That URL is sent to your upstream server, which is not accessible publically; hence, it does not work when the browser tries to fetch it.

    The problem here is that the NextJS image optimization is being relied on, at least in part, for the wrong reasons. It appears your images are hosted on some other upstream service, which is not publicly accessible on your container. So you have a requirement to reverse proxy those images through your NextJS server.

    NextJS image optimization will incidentally, and only partly, fulfil this requirement. It will only work for images which NextJS believes it needs to optimize. For those, it downloads (enabled by the internal networking, and so can do so) the image from the source, optimizes it, caches it & then serves that image from a URL on the NextJS instance.

    That means for optimized images, the URL to the cache is ultimately used for the HTML <img> tag, which works from the browser since it is an accessible URL.

    This makes it tempting to enable dangerouslyAllowSVG, which would force it to go down the same code paths for SVGs too. However, this wouldn't fix the mental model/architecture flaw -- which is the real problem. It also appears to have security implications.

    Fix: Explicitly define reverse proxy behavior

    The requirement to reverse proxy images from your upstream is its own business requirement, and you aren't supposed to lean on image optimization by itself to achieve that. NextJS image optimization is intended to provide a mechanism to optimize your images. That it acts as a reverse proxy in some cases is a technical detail, and you can't rely on that behaviour to fulfil this business req now or in future. You have a first-class requirement for this reverse proxy, so you must define it.

    We can use rewrites to achieve this. In next.config.js:

    module.exports = {
      async rewrites() {
        return [
          {
            source: '/raw/:path*',
            destination: 'http://host.docker.internal:1337/uploads/:path*'
          },
        ]
      },
    }
    

    This means a new URL on your NextJS instance /raw/<image-path> is available where image-path is the filename of the image to request from http://host.docker.internal:1337/uploads.

    Then, when using an <Image> tag, the src prop that will be passed will be a relative path to the source destination above and not include any fully qualified internal docker domain. E.g. src="/raw/example-image.svg".

    For cases where NextJS does not need to do any optimization (e.g. SVGs), it will do what it did before, which is to use the src prop directly as the src HTML attribute of the <img>. But now that URL will be valid, pointing to our /raw path, which NextJS will proxy to the upstream via our rewrites.