@miketromba/screenshot-service
v0.4.1
Published
<p align="center"> <img src="assets/banner.png" alt="Screenshot Service" width="100%" /> </p>
Readme
Quick Start
Development (Local Server)
Run the screenshot service locally with a single command (requires Bun):
# Start the server on port 3000
npx @miketromba/screenshot-service
# Or with options
npx @miketromba/screenshot-service --port 3001
# With authentication
SCREENSHOT_AUTH_TOKEN=secret npx @miketromba/screenshot-serviceProduction (Vercel + Next.js)
Deploy the screenshot service as part of your Next.js project on Vercel.
Note: This package ships raw TypeScript files. You must configure Next.js to transpile it.
npm install @miketromba/screenshot-serviceStep 1: Configure Next.js to transpile the package (next.config.js or next.config.ts):
/** @type {import('next').NextConfig} */
const nextConfig = {
transpilePackages: ['@miketromba/screenshot-service'],
}
module.exports = nextConfigStep 2: Create the API route
Next.js App Router (app/api/screenshot/route.ts):
export { GET } from '@miketromba/screenshot-service/vercel'Or for Pages Router (pages/api/screenshot.ts):
export { GET } from '@miketromba/screenshot-service/vercel'Step 3: Add function configuration (vercel.json):
{
"functions": {
"api/screenshot.ts": {
"maxDuration": 60,
"memory": 1024
}
}
}Set environment variables in Vercel dashboard:
SCREENSHOT_AUTH_TOKEN- Bearer token for authentication (optional)SCREENSHOT_HOST_WHITELIST- Comma-separated allowed hostnames (optional)
Overview
The Screenshot Service provides an API for generating screenshots of web pages using Puppeteer. It supports two deployment modes:
- Development: Local Bun server with puppeteer-cluster for concurrent processing (up to 10 simultaneous screenshots)
- Production: Vercel serverless function using puppeteer-core + @sparticuz/chromium (scales horizontally)
Features
- On-demand screenshot generation
- Configurable screenshot dimensions
- Support for multiple image formats (PNG, WEBP, JPEG)
- Adjustable image quality
- Full page or viewport screenshots
- Light/dark mode rendering via
colorSchemeparameter - Auto-scroll for lazy-loaded images and scroll-triggered animations
- Wait for all images to finish loading before capture
- Concurrent request handling (up to 10 simultaneous screenshots)
- Bearer token authentication for secure access
- Hostname whitelist validation for enhanced security
Use Cases
Internal Application Screenshots
The service is particularly useful for capturing screenshots of authenticated internal applications. For example, in a design tool where previews are protected behind authentication:
- Set up your internal application with authentication
- Configure the screenshot service with the same authentication token
- Use the service to capture authenticated views of your application
Example scenario:
# Your design tool has protected preview URLs like:
# https://design-tool.internal/preview/design-123
# These URLs require authentication to access
# Configure the screenshot service with your auth token
export SCREENSHOT_AUTH_TOKEN=your-internal-auth-token
# The service will now be able to access and capture these protected previews
curl -H "Authorization: Bearer $SCREENSHOT_AUTH_TOKEN" \
"http://localhost:3000/screenshot?url=https://design-tool.internal/preview/design-123" \
> design-preview.pngThis setup ensures that:
- Your internal application remains secure
- Only the screenshot service can access protected previews
- You can programmatically capture authenticated views of your application
Authentication
Authentication is optional and can be enabled by setting the SCREENSHOT_AUTH_TOKEN environment variable. When enabled, all endpoints except the health check (GET /) require authentication using a Bearer token. The token must be included in the Authorization header of each request.
Format: Authorization: Bearer <your-token>
If authentication is enabled and the token is missing or invalid, the server will respond with:
401 Unauthorized: Missing or invalid Authorization header format403 Forbidden: Invalid token
Note: When SCREENSHOT_AUTH_TOKEN is set, it is also used to authenticate requests to the target websites when taking screenshots. This means the service will forward your authentication token to the websites you're capturing.
API Endpoints
The API is identical for both deployment options, with different base paths:
| Deployment | Health Check | Screenshot |
|------------|--------------|------------|
| Docker/Bun | GET / | GET /screenshot |
| Vercel | GET /api | GET /api/screenshot |
Health Check
Returns server status. This endpoint does not require authentication.
Response:
{ "online": true }Take Screenshot
Authentication Required: Yes (Bearer token)
Query Parameters:
url(required): The URL to screenshot (must be a valid URL)fullPage(optional): Whether to capture the full page or just the viewport- Default: false
- Values: "true" or "false"
quality(optional): Image quality (for JPEG and WEBP only)- Default: 100
- Range: 1-100
type(optional): Output image format- Default: "png"
- Values: "png", "webp", "jpeg"
width(optional): Viewport width in pixels- Default: 1440
- Range: 1-1920
height(optional): Viewport height in pixels- Default: 900
- Range: 1-10000
colorScheme(optional): Emulate light or dark mode for sites that supportprefers-color-scheme- Values: "light", "dark"
- When omitted, the browser's default (light) is used
scrollPage(optional): Auto-scroll through the full page before capturing. Triggers lazy-loading images and IntersectionObserver-based animations (e.g. Framer MotionwhileInView, AOS, CSS reveals).- Default: "false"
- Values: "true" or "false"
waitForImages(optional): Wait for every<img>element to finish loading before taking the screenshot.- Default: "false"
- Values: "true" or "false"
Page Loading Options
The service provides several options to control when the screenshot is taken, ensuring content is fully loaded:
waitUntil(optional): When to consider page navigation successful- Default: "networkidle2"
- Values:
"load"- Wait for theloadevent (all resources loaded)"domcontentloaded"- Wait for theDOMContentLoadedevent (DOM is ready, but stylesheets/images may still be loading)"networkidle0"- Wait until there are no network connections for at least 500ms (strictest)"networkidle2"- Wait until there are no more than 2 network connections for at least 500ms (default, good balance)
waitForSelector(optional): CSS selector to wait for before taking the screenshot- Example: ".main-content" or "#hero-image"
- Timeout: 30 seconds
- Use this when you need to ensure a specific element has rendered
delay(optional): Additional delay in milliseconds after page load before taking the screenshot- Range: 0-30000 (0-30 seconds)
- Use this for animations, transitions, or slow-rendering content
Note: The service always waits for web fonts to load (document.fonts.ready) before taking screenshots to ensure text renders correctly.
Response:
- Content-Type: image/[type]
- Body: Binary image data
Error Responses:
403 Forbidden: If the URL's hostname is not in the allowed whitelist
Environment Variables
PORT: Server port number (default: 3000)NODE_ENV: Environment setting ("development" enables request logging)SCREENSHOT_AUTH_TOKEN: Optional. When set, used for two purposes:- The bearer token that clients must provide to access protected endpoints
- The authorization token forwarded to target websites when taking screenshots
MAX_CONCURRENCY: Maximum number of concurrent screenshot operations (default: 10)SCREENSHOT_HOST_WHITELIST: Comma-separated list of allowed hostnames. If empty, all hostnames are allowed
Technical Details
Local Development Server
- Built with Hono web framework
- Uses Puppeteer for browser automation
- Implements puppeteer-cluster for concurrent processing
- Input validation using Zod
- Requires Bun runtime
Vercel Serverless (Next.js)
- Requires Next.js with
transpilePackagesconfigured (ships raw TypeScript) - Serverless function with puppeteer-core
- Uses @sparticuz/chromium for serverless-optimized Chromium
- Shared validation and screenshot logic
Docker Usage (Alternative)
For production deployments where you need full control, you can also run this as a Docker container.
Building the Image
You can build the Docker image using either the bun script or directly with Docker:
# Using bun script (builds with tag: screenshot-service)
bun run docker:build
# Or directly with Docker
docker build -t screenshot-service .Running the Container
docker run -d \
-p 3000:3000 \
-e SCREENSHOT_AUTH_TOKEN=your-secret-token \
-e SCREENSHOT_HOST_WHITELIST=example.com,test.com \
-e MAX_CONCURRENCY=10 \
screenshot-serviceEnvironment Variables
All environment variables can be passed to the container using the -e flag or by using a .env file:
docker run -d \
-p 3000:3000 \
--env-file .env \
screenshot-serviceAccessing Local Services from Docker
When running the screenshot service in Docker and you need to capture screenshots of services running on your local machine, you need to:
- Add the
--add-host=host.docker.internal:host-gatewayflag when running the container - Use
host.docker.internalinstead oflocalhostin your URLs
This is because localhost inside the container refers to the container itself, not your host machine.
Example:
# ❌ Instead of:
curl -H "Authorization: Bearer $SCREENSHOT_AUTH_TOKEN" "http://localhost:3000/screenshot?url=http://localhost:3001/my-app"
# ✅ Use:
curl -H "Authorization: Bearer $SCREENSHOT_AUTH_TOKEN" "http://localhost:3000/screenshot?url=http://host.docker.internal:3001/my-app"Note: The --add-host flag is required for host.docker.internal to work. Make sure to include it in your docker run command.
Vercel Production Notes
Package Manager Compatibility
Important: This package may not work correctly with PNPM when deployed to Vercel.
PNPM uses symlinks for its node_modules structure, which can cause issues with Vercel's build process and module resolution in production. If you encounter module resolution errors or unexpected behavior when deploying to Vercel with PNPM, switch your project to use Bun or npm as the package manager instead.
# If using PNPM and experiencing issues, switch to Bun:
rm -rf node_modules pnpm-lock.yaml
bun installLimitations
- Cold starts: First request may take 5-10 seconds (browser launch)
- Timeout: 60 seconds max (configurable up to 300s on Pro plan)
- No custom fonts: Unlike local development, Vercel functions don't include custom fonts. Screenshots may render with different fonts.
- Scaling: Vercel handles scaling automatically via parallel function invocations
Local Dev vs Vercel + Next.js
| Feature | Local (npx) | Vercel + Next.js |
|---------|-------------|------------------|
| Concurrency | puppeteer-cluster (configurable) | Horizontal scaling |
| Cold start | None (always running) | 5-10 seconds |
| Custom fonts | System fonts available | Limited |
| Max timeout | Unlimited | 60-300 seconds |
| Cost | Free (local) | Pay per invocation |
| Setup | None | Requires transpilePackages config |
Example Usage
# Set your auth token
export SCREENSHOT_AUTH_TOKEN=your-secret-token
# Basic screenshot
curl -H "Authorization: Bearer $SCREENSHOT_AUTH_TOKEN" "http://localhost:3006/screenshot?url=https://example.com" > screenshot.png
# Full page JPEG screenshot with 80% quality
curl -H "Authorization: Bearer $SCREENSHOT_AUTH_TOKEN" "http://localhost:3006/screenshot?url=https://example.com&fullPage=true&type=jpeg&quality=80" > screenshot.jpg
# Custom dimension WEBP screenshot
curl -H "Authorization: Bearer $SCREENSHOT_AUTH_TOKEN" "http://localhost:3006/screenshot?url=https://example.com&width=1024&height=768&type=webp" > screenshot.webp
# Wait for all network activity to finish (strictest loading strategy)
curl -H "Authorization: Bearer $SCREENSHOT_AUTH_TOKEN" "http://localhost:3006/screenshot?url=https://example.com&waitUntil=networkidle0" > screenshot.png
# Wait for a specific element to appear before taking screenshot
curl -H "Authorization: Bearer $SCREENSHOT_AUTH_TOKEN" "http://localhost:3006/screenshot?url=https://example.com&waitForSelector=.main-content" > screenshot.png
# Add a 2-second delay for animations/transitions to complete
curl -H "Authorization: Bearer $SCREENSHOT_AUTH_TOKEN" "http://localhost:3006/screenshot?url=https://example.com&delay=2000" > screenshot.png
# Combine multiple loading options for complex pages
curl -H "Authorization: Bearer $SCREENSHOT_AUTH_TOKEN" "http://localhost:3006/screenshot?url=https://example.com&waitUntil=networkidle0&waitForSelector=#hero-image&delay=1000" > screenshot.png
# Capture a website in dark mode
curl -H "Authorization: Bearer $SCREENSHOT_AUTH_TOKEN" "http://localhost:3006/screenshot?url=https://example.com&colorScheme=dark" > screenshot-dark.png
# Capture a website in light mode
curl -H "Authorization: Bearer $SCREENSHOT_AUTH_TOKEN" "http://localhost:3006/screenshot?url=https://example.com&colorScheme=light" > screenshot-light.png
# Full-page capture with lazy image and animation support
curl -H "Authorization: Bearer $SCREENSHOT_AUTH_TOKEN" "http://localhost:3006/screenshot?url=https://example.com&fullPage=true&scrollPage=true&waitForImages=true" > screenshot-full.png