Next.js is an extremely popular framework for building React applications. Among the wide range of features it offers, image optimization is one of the most discussed topics in the community. Developers are always looking for the most performant, easy-to-use, and cost-effective solutions to handle images in their Next.js projects. In this guide, we will explore how to use imgproxy with Next.js to optimize images effectively.

TL;DR: If you want to skip the guide and jump straight to the code, you can find the complete example on GitHub.
There are plenty of ways to run imgproxy in production, but for development purposes, the easiest way is to run it as a Docker container. If you have Docker installed, just run the following command:
That’s it! imgproxy is up and running on port 8080. During this guide, we will add some configuration options to the command; however, for now, let’s keep it simple.
Next.js provides a built-in next/image component for image optimization. By default, it uses the Next.js image optimization service, which has a limited feature set and is not very flexible. Luckily, this component can be integrated with virtually any image optimization service using a custom image loader. The loader is a function that takes a source image URL, target width, and target quality as parameters and returns a URL to the optimized image. And imgproxy is a perfect candidate for this!
We are going to use an NPM package called @imgproxy/imgproxy-js-core to generate imgproxy URLs (thanks to our friends from Evil Martians). While this package is not strictly necessary, it handles some details, such as escaping the source image URL. Let’s install it:
The loader function itself is pretty simple. Let’s create a new file src/imgproxyImageLoader.ts:
There are a couple of things you can configure in this loader function:
-
imgproxy endpoint: This is the address of your imgproxy server. By default, it is set to http://localhost:8080, but you can change it by setting the NEXT_PUBLIC_IMGPROXY_ENDPOINT environment variable.
-
imgproxy base URL: This is the URL of your Next.js application. It is used to resolve relative image URLs. By default, it is set to http://host.docker.internal:8100, but you can change it by setting the NEXT_PUBLIC_IMGPROXY_BASE_URL environment variable.
Note that we use host.docker.internal instead of localhost for the imgproxy base URL. This is necessary because our imgproxy server runs in a Docker container, and localhost inside the container refers to the container itself, not your host machine. To solve this, Docker for macOS and Windows provides a special DNS name, host.docker.internal, that resolves to the host machine’s IP address. If you are running Docker on Linux, you can use the --add-host=host.docker.internal:host-gateway option to achieve the same effect.
The next step is to configure next/image to use our custom loader function. You can do this in the Next.js configuration file (next.config.js):
Now you can use the <Image /> component in your Next.js application, and it will automatically use imgproxy to optimize images:
Alternatively, you can set the loader directly in the <Image /> component:
That’s it! Now it’s time to run your Next.js application and see the optimized image in action! If everything is set up correctly, you should see the image being served through imgproxy.
The <Image /> component renders a single <img> element, which means it doesn’t take care of serving modern image formats like WebP or AVIF out of the box, leaving this task to the image optimization service. So let’s make imgproxy handle that!
To enable serving WebP and AVIF to the clients that support them, use the IMGPROXY_AUTO_WEBP and IMGPROXY_AUTO_AVIF environment variables:
When putting a caching CDN in front of imgproxy, don’t forget to add the Accept header to the cache key. This will ensure that the CDN caches images in all formats, not just the first one it receives.
An attentive reader might notice a quite unpleasant word in the loader function code: unsafe. That means that we use unsigned imgproxy URLs.
imgproxy utilizes URL signatures to prevent attackers from messing with your imgproxy instances. When URL signing is not enabled in imgproxy, it allows anyone to process any image from any source with any processing options. This poses a security risk, and we typically recommend enabling URL signing in production environments. Unluckily, this is not possible with next/image: <Image /> is a client component, and it generates image URLs on the client side. That means that the signature keys would be exposed to the client, making URL signing meaningless.
Luckily, imgproxy provides several security measures that we can use to enhance the security of our setup without URL signing.
The first step is to restrict the sources of images that can be processed by imgproxy. This can be done by setting the IMGPROXY_ALLOWED_SOURCES environment variable. This variable accepts a comma-separated list of allowed sources, and imgproxy will only process images from these sources.
For example, if you want to allow images from your Next.js application (http://host.docker.internal:8100/) and from Unsplash (https://images.unsplash.com/), update your docker run command like this:
Now, if you try to process an image from a different source, imgproxy will return an error.
Another important security measure is to restrict the processing options that can be used with imgproxy and the resulting size of the processed images. Starting version 3.29.0, imgproxy offers a set of configuration options that allow you to do just that:
- IMGPROXY_MAX_RESULT_DIMENSION controls the maximum width and height of the processed images. If an image exceeds this size, imgproxy will downscale it to fit within the specified dimensions.
- IMGPROXY_ALLOWED_PROCESSING_OPTIONS controls which processing options can be used in imgproxy URLs.
Since next/image only allows you to specify the width and quality of the image, you can restrict the allowed processing options to just w (width) and q (quality). Assuming you want to limit the maximum width and height of the processed images to 2000 pixels, you can update your docker run command like this:
Another approach to restrict the processing options is to use imgproxy in presets-only mode. In this mode, imgproxy accepts only presets (predefined sets of processing options) instead of individual processing options. This approach is less flexible than the previous one, but it is more secure, as it allows you to restrict not only the processing options but also their values.
For example, you want to allow image width to be only 200, 400, 600, or 800 pixels and quality to be only 1, 40, or 80. You can update your docker run command like this, defining a set of presets and enabling the presets-only mode:
Now, if you reload your Next.js application, you’ll see that your images are no longer working. This is because our loader function generates URLs with processing options, but imgproxy is now in presets-only mode. Let’s update the loader function to use presets instead of processing options:
Now, the loader function picks presets based on the requested width and quality and generates URLs that imgproxy can process in presets-only mode.
While next/image is a well-integrated solution for image optimization in Next.js, it has a number of limitations. We already mentioned that it makes URL signing meaningless, but there is a larger elephant in the room: next/image only allows you to specify the width and quality of the image. Using imgproxy and utilizing only these two parameters is like using a Ferrari to drive to the grocery store. Let’s fix this!
Next.js 13 introduced support for React Server Components (RSC). Unlike client components, RSCs are rendered on the server and have access to the server-side environment variables. This appears to be the right place to generate perfectly secure signed imgproxy URLs with any desired processing options.
Our friends from Evil Martians have created another handy NPM package, @imgproxy/imgproxy-node, which drastically simplifies the process of generating signed imgproxy URLs in Node.js applications. Let’s install it:
Now, we can create a server component that generates signed imgproxy URLs and uses them in a <picture> element. Let’s create a new file src/components/Imgproxy.tsx:
This component depends on a couple of CSS classes, so let’s create a tiny CSS module src/components/Imgproxy.module.css:
Now we have a server component that generates signed imgproxy URLs, supports all the processing options that imgproxy offers, and assembles a <picture> element with multiple sources for different image formats and DPRs (device pixel ratios)!
You may notice that our component does not contain any code that signs the imgproxy URLs or uses secret keys. This is because the @imgproxy/imgproxy-node package automatically uses the keys from the environment variables IMGPROXY_KEY and IMGPROXY_SALT to sign the URLs. Let’s create a .env.local file in the root of your Next.js project and add the keys there:
These are just example keys, and we recommend generating your own keys for production use. Check out the imgproxy documentation for more information on how to generate secure keys.
Your Next.js application automatically loads environment variables from the .env.local file, and the @imgproxy/imgproxy-node package will use these keys to sign the URLs. Now, we need to configure the imgproxy server to use the same keys. Docker can read environment variables from files using the --env-file option, so let’s update our docker run command to include the keys:
But enough talking, let’s see our component in action! You can use it in your Next.js application like this:

That’s still just width and height; that’s not enough! Let’s resize the image to fill the container!

Let’s crop out a part of the image!

Let’s add a blur effect!

As you can see, the full power of imgproxy is now at your disposal!
In this guide, we explored how to use imgproxy with Next.js to optimize images effectively and securely. We started by extending the built-in next/image component. Then, we moved on to creating a custom server component that leverages the full power of imgproxy. Simply choose the approach that best suits your needs and enjoy the benefits of image optimization in your Next.js applications. And if you have any questions or suggestions, feel free to reach out to us!
We published a complete example of a Next.js application using imgproxy on GitHub. Check it out to see imgproxy in action!