Spoiler Alert: If you’re serving images directly from a CDN like Cloudinary or S3, this post might not be for you; you likely won’t face this issue. But if you’re running your own production or development environment with Dockerized Next.js, Django, and Nginx, buckle up, friend. You might just save yourself a few hours of headaches.
🧩 The Problem: Images Playing Hide & Seek
So here’s the scene: I’m building a full-stack app using Next.js (frontend), Django (backend), and Nginx (reverse proxy), all running smoothly in their own cosy Docker containers. They even share a common volume for media and static files. Pretty neat, right?
Now, when I upload a profile picture via a Django endpoint, it gets saved in the media/
folder. Because of the shared volume, the image is instantly accessible to Nginx as well. Which means I can hit:
-
http://localhost:8000/media/profile_pics/user.png
(via Django) - or just
http://localhost/media/profile_pics/user.png
(via Nginx)
So far, so good.
Then I did something completely logical… I passed that URL directly into the Next.js <Image />
component:
<Image src="http://localhost/media/profile_pics/user.png" alt="User Profile" />
But what did I get? A glorious 500 Internal Server Error. Ta-da.
🔍 So… What Was Going Wrong?
Let’s break it down.
When I passed the image URL to <Image />
, Next.js’s Image Optimiser internally rewrote the request to something like:
/_next/image?url=http://localhost/media/profile_pics/user.png&w=1920&q=75
The catch? Inside the Next.js container, localhost
refers to itself, not to the Nginx container or the host machine. So when Next.js tried to fetch the image from localhost
, it hit a dead end because it was looking inside its container, where the image doesn’t exist. It was like asking a cat to bark. Not happening!!
🧪 The “Aha!” Moment (a.k.a. My DIY Proxy Fix)
I could’ve switched to using a CDN… but I wanted to stick with my setup for now. So here’s what I did:
The Plan
I built a custom wrapper around the Next.js <Image />
component called <ProxyImage />
. This little genius rewrites any “localhost” URLs to use a Docker-resolvable hostname like http://nginx
, which points to the right container where the image exists.
🛠️ The Solution (Step-by-Step)
1. Update Your .env
NEXT_PUBLIC_REWRITE_IMAGE_BASES=http://localhost,http://127.0.0.1
NEXT_PUBLIC_IMAGE_REPLACEMENT=http://nginx
This tells the app which base URLs to replace (like http://localhost
) and what to replace them with (http://nginx
).
2. Update Your config.ts
const config = {
imageRewrite: {
baseUrls: (process.env.NEXT_PUBLIC_REWRITE_IMAGE_BASES || "").split(","),
replacement: process.env.NEXT_PUBLIC_IMAGE_REPLACEMENT || "",
},
};
Let’s keep things dynamic and DRY, shall we?
3. Build the ProxyImage
Component
import Image, { ImageProps } from 'next/image';
import config from '@/config';
const { baseUrls, replacement } = config.imageRewrite;
function rewriteSrc(src: string): string {
for (const base of baseUrls) {
if (src.startsWith(base)) {
return src.replace(base, replacement);
}
}
return src;
}
interface ProxyImageProps extends Omit<ImageProps, 'src'> {
src: string;
}
export const ProxyImage = ({ src, ...props }: ProxyImageProps) => {
const fixedSrc = rewriteSrc(src);
return <Image {...props} src={fixedSrc} />;
};
Now just use <ProxyImage />
wherever you need optimized images.
4. Allow External Image Domains in next.config.js
module.exports = {
images: {
remotePatterns: [
{
protocol: 'http',
hostname: 'nginx',
pathname: '/media/**',
},
],
},
};
This tells Next.js: “Hey, it’s cool to optimise images from http://nginx
.”
✅ Why This Worked For Me
- Docker-Friendly: Rewrites URLs to something your container can resolve.
- Leverages Next.js Optimisation: You still get all the goodies like lazy loading, resizing, etc.
- Flexible Config: URLs and replacement targets are environment-based.
- No Extra Services Needed: No need to spin up a CDN just for local dev.
✨ Bonus Thoughts
There are other ways to solve this problem, like skipping optimisation altogether for internal images, preloading them, or routing through an API, but this setup felt like the most robust and flexible option for my Dockerized local stack.
If you’ve got a better solution, I’d love to hear it! You do you, dev.
🚀 TL;DR
- If your images are served from localhost in Docker, Next.js’s Image component might fail.
- The fix? Create a wrapper that rewrites
localhost
URLs to a valid Docker hostname likenginx
. - Your images load. Your errors vanish. Your dev life becomes 5% happier.