When building modern web applications, images play a crucial role in the user experience. Optimizing these images for different devices, screen sizes, and network conditions is essential for performance, especially with Next.js, which includes a built-in image component that attempts to solve the problem of handling responsive images. However, using paid cloud services for image optimization, like Vercel or Cloudinary, might not be the best option for everyone.
In this blog post, we’ll explore why image optimization in Next.js is challenging, discuss popular cloud-based solutions (and their drawbacks), introduce Thumbor for self-hosted image optimization, and guide you through deploying it with NGINX as a cache layer. We’ll also show how to use the Next.js Image component with Thumbor.
Why Image Optimization in Next.js is Hard
Next.js provides the Image component to handle various complexities of responsive images, such as lazy loading, automatic resizing, and serving different image formats like WebP. This feature is awesome for performance, but it comes with its challenges:
Key Challenges:
- Custom Image Loaders: Next.js’s default image loader works seamlessly with Vercel (if you’re hosting on Vercel). But what if you host elsewhere? You’ll need to use custom image loaders and configure them properly for image optimization.
- Third-party Dependence: While using services like Vercel’s built-in optimization or Cloudinary seems convenient, you’re relying on a third-party service, which can introduce costs, vendor lock-in, and limited control.
- Scaling for Global Users: Optimizing images for users across different regions can be tough. You need to handle caching and deliver images quickly regardless of location.
- Custom Storage: If you store images in S3 or another cloud service, you may need to figure out how to serve these optimized images without excessive network requests or performance degradation.
These challenges make it clear that image optimization is not a one-size-fits-all problem.
Current Options (and Why They Suck)
Most developers default to using services like Vercel, Cloudinary, or Imgix for image optimization. While they are popular and seem like convenient solutions, there are some major drawbacks.
Vercel Image Optimization
- Pros: Fully integrated with Next.js, automatic responsive images, great for small to medium apps.
- Cons: It’s paid! And not cheap. Additionally, you’re tied to Vercel’s hosting and usage limits. If you scale out and need custom image hosting (like S3), it becomes tricky.
Cloudinary
- Pros: Cloudinary supports various image formats, transformations, and an easy-to-use API.
- Cons: Expensive pricing for bandwidth, transformations, and storage. Vendor lock-in is a huge issue here—once you’re in, migrating out is complex and painful.
Imgix
- Pros: Provides powerful transformation tools and global image delivery.
- Cons: Same as Cloudinary: cost, vendor lock-in, and complexity. Their pricing models are hard to predict if your app grows.
These services add convenience, but at the cost of control and sometimes unpredictable scaling costs. For developers looking for more control over their infrastructure, self-hosting becomes an attractive option. This is where Thumbor comes in.
What Thumbor Is and How It Can Optimize Images from S3 and Cache Behind NGINX
Thumbor is an open-source smart imaging service that enables you to resize, optimize, and cache images on the fly. It’s lightweight, flexible, and a great choice for developers who want to handle image optimization without relying on expensive external services.
Key Features of Thumbor:
- On-the-fly Image Resizing: Just specify the desired dimensions in the URL, and Thumbor will handle the resizing.
- Smart Cropping: Thumbor can automatically detect the important parts of the image to focus on when cropping.
- Image Format Conversion: It can convert images into WebP for browsers that support it, automatically reducing file size.
- Caching: Thumbor can be easily integrated with NGINX to cache the optimized images, reducing the load on your server and speeding up delivery.
- S3 Integration: Thumbor can fetch images directly from S3 and optimize them before serving them to users.
Why Thumbor Over Cloud Services?
- Cost Efficiency: You host it yourself, no more vendor fees.
- Complete Control: You decide the storage backend (like S3) and caching strategy (like NGINX).
- Scalability: Thumbor can be scaled horizontally to serve thousands of images if needed.
Thumbor + NGINX Caching
By integrating NGINX with Thumbor, you can cache the images locally. This means once Thumbor optimizes an image, NGINX can serve the cached version to reduce redundant processing.
How to Deploy your own image optimization with NGINX Caching on Kubernetes
To deploy Thumbor and NGINX on Kubernetes, we will use a combination of a Kubernetes Deployment and an NGINX sidecar container. Additionally, we’ll set up an AWS CLI sidecar to sync images from S3 to a local directory. This method allows you to fetch images from S3, optimize them using Thumbor, and cache the optimized images with NGINX.
Here’s how you can set it up using a Kubernetes YAML Deployment.
Step 1: Kubernetes Deployment YAML for Thumbor, NGINX, and S3 Sync
Key Parts of the Deployment:
- Thumbor container:
- Uses the file loader to load images from the /data/images directory, where the S3 images are synced.
- Stores the optimized and transformed images in the /data/cache directory for quick access on subsequent requests.
- NGINX container:
- Acts as a reverse proxy and caching layer for Thumbor. Requests are cached in NGINX after being processed by Thumbor, reducing the load on the Thumbor service for repeated image requests.
- AWS CLI Sidecar:
- Syncs images from S3 every 10 minutes to the /data/images directory, where Thumbor picks them up for processing.
Step 2: ConfigMap for NGINX Configuration
Here’s an example ConfigMap for NGINX that proxies requests to Thumbor and caches them:
Step 3: Use Next.js with the Thumbor Deployment
Now that Thumbor is up and running on Kubernetes, you can configure the Next.js Image component to use Thumbor as its custom image loader. This will allow Next.js to request optimized images from Thumbor.
Here’s an example of how to set up the Next.js Image Component with the custom loader:
Step 4: Deploy and Test
Deploy the Kubernetes YAML configuration and ensure that:
- Images are synced from S3 to Thumbor using the AWS CLI sidecar.
- NGINX caches optimized images and proxies requests to Thumbor.
- Next.js uses the Thumbor URL for all images, delivering optimized versions based on the requested width.
Recap of How It Works:
- Thumbor is responsible for dynamically resizing and optimizing images.
- NGINX serves as a caching layer to reduce redundant Thumbor requests.
- AWS CLI sidecar syncs images from S3 to the Thumbor pod’s local filesystem.
- Next.js Image Component uses Thumbor for image optimization, requesting different sizes and qualities depending on the device and network.
By self-hosting Thumbor and integrating it with NGINX for caching and S3 for storage, you gain full control over your image optimization pipeline. Plus, it’s highly scalable and cost-effective compared to third-party services like Cloudinary or Vercel’s image optimization.
Why Self-hosting Image Optimization is Powerful
While cloud services like Vercel, Cloudinary, and Imgix provide convenience, self-hosting with tools like Thumbor gives you:
- Full Control: You decide where and how your images are stored, processed, and served.
- Scalability: You can scale Thumbor instances as your application grows without worrying about third-party limits.
- Cost-Effectiveness: Instead of paying for every image transformation or bandwidth with Cloudinary, you control the infrastructure and minimize recurring costs.
Final Thoughts
By self-hosting Thumbor, you’re free from vendor lock-in and high fees while retaining full control over how your images are optimized and served. With tools like NGINX for caching and S3 for storage, your Next.js app can serve optimized images just as efficiently as with paid cloud services—without the costs.