A high-performance image proxy and web services toolkit built with modern TypeScript. Lens provides a comprehensive suite of web utilities including image processing, screenshot capture, font serving, and more.
- πΌοΈ Image Proxy: IPX-powered image processing with resize, format conversion, optimization
- πΈ Screenshot Capture: Fast website screenshot service with resource optimization
π °οΈ Font Service: Google Fonts compatible API with multiple providers- π¨ Open Graph Images: Dynamic OG image generation
- π― Favicon Extraction: Smart favicon extraction from websites
- π€ Gravatar Proxy: Cached Gravatar avatar proxy with email or hash input
- β‘ Redis Caching: Redis-powered caching with 24-hour intelligent caching
- π Resource Optimization: Optimized page loading with blocked unnecessary resources
- π‘οΈ Rate Limiting: Rate limiting for expensive operations
- βοΈ Cloud Native: Support for Railway, Zeabur, and other persistent runtime platforms
- π Graceful Degradation: Automatic fallbacks for missing services
- Node.js 22+
- pnpm 10+ (recommended) or npm
# Clone the repository
git clone https://github.com/bysages/lens.git
cd lens
# Install dependencies
pnpm install
# Copy environment configuration
cp .env.example .env# Start development server
pnpm dev
# Build for production
pnpm build
# Preview production build
pnpm previewThe server will start on http://localhost:3000 by default.
All configurations are optional and will gracefully degrade:
# Image proxy security
ALLOWED_DOMAINS=example.com,cdn.example.com
# Caching (significantly improves performance)
REDIS_URL=redis://localhost:6379See .env.example for all available options.
All cached responses include X-Cache headers (HIT/MISS) and ETag headers for conditional 304 responses.
Transform and optimize images on-the-fly:
GET /img/{modifiers}/{image_url}
Examples:
# Resize to 300px width, convert to WebP
/img/w_300,f_webp/https://example.com/image.jpg
# Create 200x200 square thumbnail
/img/s_200x200,q_80/https://example.com/image.png
# Smart crop with high quality
/img/w_400,h_300,c_fill,q_95/https://example.com/photo.jpgSupported Modifiers:
w_XXX- Widthh_XXX- Heights_XXXxYYY- Size (width x height)f_FORMAT- Format (webp, jpg, png, avif)q_XXX- Quality (1-100)c_MODE- Crop mode (fill, fit, pad)
Performance Features:
- Redis caching for optimal performance
- Automatic format optimization and compression
Capture website screenshots:
GET /screenshot?url={website_url}&options
Examples:
# Basic screenshot
/screenshot?url=https://example.com
# Mobile screenshot with custom size
/screenshot?url=https://example.com&width=375&height=667&mobile=true
# Full page with WebP format
/screenshot?url=https://example.com&fullPage=true&format=webp&quality=90Parameters:
url- Website URL (required)width- Viewport width (default: 1280)height- Viewport height (default: 720)format- Output format (png, jpeg, webp)quality- JPEG quality (1-100)fullPage- Capture full page (true/false, default: false for viewport capture)mobile- Mobile viewport (true/false)darkMode- Dark mode (true/false)deviceScaleFactor- Device scale factor (default: 1)waitUntil- Navigation wait condition (load, domcontentloaded, networkidle, default: domcontentloaded)delay- Additional delay in milliseconds after page load (0-1000)
Performance Notes:
- Screenshots are cached for 1 day with rate limiting
- Identical requests return cached results with sub-second response times
- Resource optimization (blocking fonts, media, websockets) speeds up screenshot generation by 60-80%
Google Fonts compatible API with both v1 and v2 endpoints:
GET /css?family={font_family}&display=swap
GET /css2?family={font_family}&display=swap
API Differences:
| Feature | /css (v1) |
/css2 (v2) |
|---|---|---|
| Multiple fonts | family=A|B (pipe separator) |
family=A&family=B (repeated parameter) |
| Style syntax | FontName:400,700 (old) |
FontName:wght@400;700 (new, strict) |
| Variable fonts | β Not supported | β
Full support with ranges (wght@200..900) |
| Base URL | fonts.googleapis.com/css |
fonts.googleapis.com/css2 |
Parameters:
family- Font family name (required)display- Font display strategy (default: swap)subset- Font subset (default: latin)provider- Font provider (google, bunny, fontshare, fontsource, default: google)proxy- Use proxy for font files (true/false, default: false)
Examples:
# Basic font (v1 API - pipe separator)
/css?family=Roboto:wght@400;700|Open+Sans:wght@300;400;600&display=swap
# Multiple fonts (v2 API - repeated family parameter)
/css2?family=Inter:wght@400;700&family=Roboto:wght@300;400&display=swap
# Variable fonts with weight range (v2 only)
/css2?family=Inter:wght@200..800&display=swap
# Font metadata
/webfonts?sort=popularity&category=sans-serifRecommendations:
- Use
/cssfor legacy compatibility with old Google Fonts syntax - Use
/css2for modern applications with variable fonts and better optimization - Both endpoints support all font providers (Google, Bunny, Fontshare, Fontsource)
Generate dynamic OG images:
GET /og?title={title}&description={description}
Examples:
# Basic OG image
/og?title=Welcome&description=Get started with Lens
# Custom styling
/og?title=Hello World&theme=dark&fontSize=72&width=1200&height=630Caching:
- Generated OG images are cached for 24 hours
- Identical requests with same parameters return cached results instantly
Extract high-quality favicons:
GET /favicon?url={website_url}&size={size}
Examples:
# Extract favicon
/favicon?url=https://example.com
# Custom size
/favicon?url=https://example.com&size=64Features:
- Smart favicon extraction from multiple sources (PWA manifests, Apple touch icons, HTML tags)
- 30-day server-side caching with ETag/304 support
- Automatic fallback to generated favicon if none found
Get Gravatar avatars by email, MD5 hash, or path. All query parameters (except email/hash) are forwarded directly to Gravatar. You can switch from Gravatar by simply replacing the URL prefix.
GET /gravatar/{md5}?{gravatar_params}
GET /gravatar?email={email}
GET /gravatar?hash={md5}&{gravatar_params}
Examples:
# By path (drop-in replacement for Gravatar URLs)
/gravatar/{md5}?size=200
# By email
/gravatar?email=user@example.com
# By MD5 hash
/gravatar?hash={md5}
# With Gravatar parameters
/gravatar?email=user@example.com&size=200&default=identicon&rating=pgParameters:
{md5}- MD5 hash in URL path (drop-in replacement mode)email- Email address (will be MD5 hashed automatically)hash- Pre-computed MD5 hash (alternative to email)- All other parameters are forwarded to Gravatar API
Lens is built with modern web standards and follows clean architecture principles:
- Runtime: Node.js 22+ with Nitro
- Language: TypeScript with strict type safety
- Image Proxy: IPX with Sharp
- Browser Automation: Playwright with singleton browser and isolated contexts
- Caching: Redis with unstorage
- KISS (Keep It Simple): Simple, focused solutions over complex abstractions
- DRY (Don't Repeat Yourself): Shared utilities and configurations
- Graceful Degradation: Fallbacks for missing dependencies
- Type Safety: Comprehensive TypeScript coverage
- Performance First: Optimized for speed and efficiency
- Node.js 22+ runtime
- pnpm 10+ package manager
- Redis (optional, for distributed caching)
# Build the application
pnpm run build
# Start production server
pnpm run preview# Copy environment variables
cp .env.example .env
# Edit .env file to configure your settings
# nano .env
# Start the application
docker-compose up -d
# View logs
docker-compose logs -f app
# Stop the application
docker-compose downThe application will be available at http://localhost:3000
The stack includes:
- Lens application on port 3000
- Redis for distributed caching (optional but recommended)
# Pull the official image
docker pull bysages/lens:latest
# Run container with host network (recommended for accurate IP logging)
docker run -d \
--name lens \
--network host \
--env-file .env \
--restart unless-stopped \
bysages/lens:latest
# View logs
docker logs -f lens
# Stop the container
docker stop lens
docker rm lens# Build Docker image
docker build -t lens .
# Run container
docker run -d \
--name lens \
--network host \
--env-file .env \
--restart unless-stopped \
lensβ Fully Supported:
- Vercel - Native support with automatic optimization
- Cloudflare Containers - Docker container deployment with persistent runtime
- Traditional VPS/Dedicated servers
- Docker containers
- Render.com
- DigitalOcean App Platform
- Heroku
- Any platform with persistent Node.js 22+ runtime
β Not Supported:
- Cloudflare Workers/Pages
- Netlify Functions
- AWS Lambda
- Azure Functions
Why not serverless? This application needs:
- Persistent browser instance (Playwright)
- Native dependencies (Sharp, chromium)
- Long-running processes (screenshots, image processing)
- Persistent storage (caching)
- Connection reuse (performance)
Vercel Exception: Vercel is fully supported through automatic environment detection and native platform integration.
All configurations are optional:
# Image Proxy Security
ALLOWED_DOMAINS=example.com,cdn.example.com
# Caching (significantly improves performance)
REDIS_URL=redis://localhost:6379See .env.example for all available options.
MIT License - see LICENSE file for details.
Built with β€οΈ by By Sages