TL;DR: Built a production-ready image optimization service with Go + libvips, featuring a Next.js UI, CLI tool, spritesheet packing with game engine support, API key authentication, rate limiting, and comprehensive Swagger documentation. Zero server storage, fully ephemeral processing, live at sosquishy.io.
Like most side projects, this one started with frustration.
We started this to help my son out, who was working on images for his own projects and trying to squeeze every byte out he could from the loading process. He kept running into the same limitations: subscription walls, file size caps, or tools that destroyed image quality. I needed something better, something I could control, and something that respected my privacy.
So I built it.
The Goal
Started simple: take any image, make it smaller and faster without sacrificing quality. Then I saw what libvips could do — the performance, the quality, the flexibility — and realized this could be more.
It grew into:
- A web UI for drag-and-drop optimization
- A REST API for programmatic access
- A CLI tool for batch processing
- A spritesheet packer for game development with deduplication
- A developer platform with API keys, rate limiting, and Swagger docs
All while maintaining a core principle: zero server-side storage. Images are processed entirely in-memory and discarded immediately. No logs, no history, no tracking.
Tech Stack: Why Go + libvips?
The Backend: Go + Fiber
I chose Go for the API backend for several reasons:
- Performance: Go’s concurrency model handles multiple image processing requests efficiently
- libvips integration: The bimg library provides excellent Go bindings for libvips
- Deployment simplicity: Single binary, small Docker images, minimal runtime dependencies
- Type safety: No runtime surprises when handling API parameters
The Fiber framework provided a familiar Express-like API with Go’s performance characteristics.
Why libvips?
Initially, I considered ImageMagick (via Go bindings), but libvips proved superior:
- Speed: 4-8x faster than ImageMagick for typical operations
- Memory efficiency: Streaming pipeline architecture uses minimal RAM
- Quality: Produces visually superior results, especially for resizing
- Format support: JPEG, PNG, WebP, AVIF, GIF with format-specific optimizations
The combination of libvips + oxipng for PNG post-processing delivers an additional 15-40% compression on top of libvips’ already excellent PNG optimization.
The Frontend: Next.js + TypeScript
The web UI needed to be fast, responsive, and easy to deploy. Next.js provided:
- Static export capability: Deploy to GitHub Pages as static files
- TypeScript safety: Type-safe API client with auto-completion
- Modern React patterns: Hooks, context, and clean component architecture
- Tailwind CSS: Rapid UI development with consistent design
Key UI features:
- Interactive before/after comparison slider
- Zoom and pan functionality (up to 4x magnification)
- Dark mode with automatic detection
- Shareable results via Web Share API
- Real-time file size reporting
Architecture and Features
Image Processing Core
The service supports multiple optimization strategies:
Formats Supported:
- JPEG (progressive encoding, Huffman optimization, chroma subsampling)
- PNG (compression levels 0-9, interlacing, palette mode, oxipng post-processing)
- WebP (lossless mode, compression effort tuning, encoding method selection)
- AVIF (20-30% better compression than WebP, cutting-edge format support)
- GIF (animated GIF preservation)
Processing Features:
- Quality/compression control (1-100 scale)
- Intelligent resizing with aspect ratio preservation
- Metadata stripping (EXIF removal for privacy)
- Color space management (automatic sRGB conversion)
- Smart optimization (returns original if optimized version is larger)
API Design
The REST API exposes multiple endpoints for different use cases:
# Single image optimization (file upload or URL)
POST /optimize
Content-Type: multipart/form-data
Authorization: Bearer YOUR_API_KEY
# Batch optimization (multiple images)
POST /batch-optimize
Content-Type: multipart/form-data
Authorization: Bearer YOUR_API_KEY
# Spritesheet packing (pack multiple sprites into optimized sheets)
POST /pack-sprites
Content-Type: multipart/form-data
Authorization: Bearer YOUR_API_KEY
# Spritesheet optimization (import existing sheet, deduplicate, repack)
POST /optimize-spritesheet
Content-Type: multipart/form-data
Authorization: Bearer YOUR_API_KEY
# Health check
GET /health
Input flexibility:
- File uploads via multipart/form-data
- URL-based fetching with domain whitelist validation (cloudinary, imgur, unsplash, pexels)
- 10MB maximum file size limit
- 10-second timeout for URL requests
Security and rate limiting:
- SQLite-backed API key management with cryptographically secure generation
- Bearer token authentication
- Sliding window rate limiter (100 requests/minute per IP)
- Comprehensive SSRF protection (domain whitelist + private IP blocking)
- Configurable via environment variables
CLI Tool: imgopt
For developers who prefer command-line workflows, the imgopt CLI provides batch processing:
# Optimize all images in a directory
imgopt -input ./photos -output ./optimized -quality 85 -format webp
# Custom API endpoint
imgopt -input ./photos -api https://api.sosquishy.io/optimize
Features:
- Progress tracking for batch operations
- Configurable quality, dimensions, and output format
- Configuration file support (
.imgoptrc) for project-specific defaults - Human-readable output with file size formatting
- Summary statistics (total saved, compression ratio)
Developer Experience: Swagger/OpenAPI Documentation
Every endpoint is fully documented with interactive Swagger UI at /swagger/index.html.
The documentation includes:
- Request/response schemas with examples
- Parameter descriptions and validation rules
- Error response formats
- Authentication requirements
- Rate limiting information
Generated automatically from code annotations using swaggo/swag, ensuring docs stay synchronized with implementation.
Privacy and Security by Design
One of the core principles of this project: your images are your business, not mine.
Zero Server Storage:
- All image processing happens entirely in-memory
- Images are never written to disk (except temporary OS buffers managed by libvips)
- No optimization logs, no analytics, no tracking
- Ephemeral processing with automatic cleanup
Security Measures:
- HTTP Security Headers: X-Content-Type-Options, X-Frame-Options, X-XSS-Protection, Referrer-Policy, and HSTS (over HTTPS)
- SSRF Protection: Domain whitelist combined with private IP blocking (loopback, RFC 1918 ranges, cloud metadata endpoints)
- Production Error Handling: Environment-aware error responses prevent internal details from leaking
- API Key Security: Cryptographically secure key generation (256-bit entropy) with SQLite-backed storage
- Input Validation: Comprehensive validation on all parameters with content-type verification
- Resource Limits: Size limits (10MB) and request timeouts prevent memory exhaustion and slowloris attacks
GDPR Compliant:
- No personal data collection
- No cookies
- No persistent storage of user content
- No third-party analytics or tracking scripts
Deployment and Infrastructure
Frontend: GitHub Pages
- Next.js static export via GitHub Actions
- Custom domain: sosquishy.io
- Zero server costs for frontend hosting
API: Render.com (See updates below)
- Docker container deployment
- Persistent SQLite database on mounted disk volume
- Environment variable configuration
- Automatic HTTPS
- Custom domain:
api.sosquishy.io
Update (Oct 2024): The API has since been migrated from Render → Fly.io (to address memory constraints) → Hetzner (for cost optimization). See Migrating from Render to Fly.io and Migrating to Hetzner for the full migration story. Current hosting: Hetzner VPS.
Local Development:
- Docker Compose for full-stack setup
- Tilt support for enhanced development workflow
- Hot reloading for both frontend and backend
Real-World Performance
Testing with typical web images shows significant improvements:
JPEG Optimization:
- Original: 2.4 MB (high-quality photo)
- Optimized (quality 85): 389 KB
- Savings: 83.8% with minimal perceptual quality loss
PNG to WebP Conversion:
- Original PNG: 1.1 MB
- WebP (quality 90): 156 KB
- Savings: 85.8%
PNG + oxipng Post-Processing:
- libvips PNG: 245 KB
- After oxipng: 167 KB
- Additional savings: 31.8%
What I Learned
1. libvips is Seriously Underrated
Coming from Node.js where sharp (libvips wrapper) is popular, I still underestimated the performance boost in Go. Streaming architecture + memory efficiency made concurrent requests trivial.
2. Privacy Became a Feature
Not storing images wasn’t just security — it became a selling point. Users care about privacy with potentially sensitive images. Ephemeral processing eliminated entire categories of security concerns.
3. API-First Design Pays Off
Built the API before the UI. This forced better decisions. The web interface became just one client, making the CLI tool trivial to add later. Swagger docs became the single source of truth.
4. Go Deployment is Bulletproof
Single binary, minimal dependencies. Docker images ~50MB. No dependency hell, no runtime mismatches, no surprise breakages.
5. Static Export Simplifies Everything
Next.js static export = no Node.js server needed. Entire frontend is HTML/CSS/JS on GitHub Pages. Zero server maintenance, zero costs, infinite UI scalability.
Recent Updates: Spritesheet Packing for Game Development
After the initial launch, I added comprehensive spritesheet packing capabilities — a feature my son specifically requested for his Friday Night Funkin’ mod development.
Spritesheet Packer
The spritesheet packer uses the MaxRects bin packing algorithm (BSSF heuristic) to achieve 85-95% space utilization when combining multiple sprite images into optimized texture atlases.
Key Features:
- Multi-format input: PNG, JPEG, GIF, WebP sprites
- Configurable packing: Padding, power-of-2 dimensions, max sheet size
- Transparency trimming: Automatic removal of transparent borders
- Game engine support: Export metadata in 9+ formats
- Multi-sheet output: Automatically splits into multiple sheets when needed
Supported Output Formats:
- Sparrow/Starling XML - HaxeFlixel, Friday Night Funkin’
- TexturePacker XML - Generic XML format
- Cocos2d plist - Cocos2d-x game engine
- Unity - TextureImporter metadata
- Godot - AtlasTexture resource format
- JSON - Generic coordinate data
- CSS - Background-position sprite classes
- CSV - Simple coordinate export
- XML - Generic XML format
Spritesheet Import & Optimization
For game developers working with existing spritesheets, the optimizer can:
- Import existing spritesheets with Sparrow XML metadata
- Extract individual frames using XML coordinates
- Deduplicate sprites with pixel-perfect byte-level comparison (70-75% reduction on typical FNF spritesheets)
- Repack optimally using MaxRects algorithm
- Export in any format - convert between game engine formats
Real-world performance:
Testing with Friday Night Funkin’ spritesheets:
- Mario sprite: 165 frames → 41 unique (75% duplicate reduction)
- Turmoil sprite: 104 frames → 31 unique (70% duplicate reduction)
- Forest King: 100+ frames optimized and repacked into efficient atlases
This workflow solved a major pain point in FNF modding where artists often create animations with many duplicate frames (idle poses, held notes, etc.), wasting texture memory and loading time.
Why This Matters for Game Development
Texture Memory Optimization:
- Smaller spritesheets = less GPU memory usage
- Better packing efficiency = fewer draw calls
- Deduplication = significant file size reduction
Cross-Engine Compatibility:
- Convert between HaxeFlixel, Unity, Godot, Cocos2d formats
- No manual coordinate recalculation
- Preserve sprite metadata (frame offsets, original dimensions)
Developer Workflow:
- Command-line batch processing for build pipelines
- Web UI for quick optimizations
- API integration for automated asset pipelines
The 8MB upload limit supports even large game spritesheets, and the ephemeral processing means your game assets stay private.
What’s Next
Service is live and production-ready. Some ideas for later:
Planned:
- Spritesheet UI for the import/optimization workflow
- More XML formats for additional game engines
- Smart format selection based on image content
- Per-API-key metrics dashboard
- Custom optimization profiles (“social media”, “hero image”, etc.)
Maybe:
- Monetization tiers (currently free)
- CDN integration for cached delivery
- WebSocket support for large batch progress
- Atlas auto-generation from sprite animations
Try It Out
Web UI: sosquishy.io API Docs: api.sosquishy.io/swagger/index.html Source Code: github.com/keif/image-optimizer
You can build a fast, privacy-focused image optimization service without complex infrastructure or expensive third-party services. Go’s performance + libvips’ quality + modern frontend tooling = developer-friendly platform that respects privacy.
If you’re building something that processes images, hopefully this is a useful reference for balancing performance, privacy, and developer experience.