Dark mode isn't a trend anymore — it's the default. Learn how to capture both light and dark screenshots with a single API parameter, inject custom dark themes, and generate dual OG images for social media.
Stop designing Open Graph images by hand. Learn how to use screenshot APIs to automatically generate beautiful, dynamic OG images for every page on your site.
Self-hosting Puppeteer sounds easy until you're debugging zombie Chrome processes at 3 AM. Here's why dedicated screenshot APIs exist and when they make sense for your project.
How we reduced p95 response times by 10x with intelligent caching, CDN integration, and browser pool management. A practical guide to making screenshot APIs fast.
Automating OG Image Generation with Screenshot APIs
+
March 3, 20267 min read
+
+
You've just published a new blog post. You share it on Twitter, LinkedIn, and Slack. But instead of a rich preview with a beautiful image, your link shows up as a sad little text snippet — or worse, a broken image placeholder. Sound familiar?
+
+
Open Graph (OG) images are the social preview cards that appear when someone shares your URL. They're one of those things that seem trivial until you realize they directly impact click-through rates. Posts with compelling preview images get 2-3x more engagement than plain text links. Yet most developers either skip them entirely or spend hours manually designing them in Figma for each page.
+
+
There's a better way: use a screenshot API to generate OG images automatically, on the fly, for every single page on your site.
+
+
The OG Image Problem
+
+
Let's start with why this is harder than it should be. The Open Graph protocol is simple — you add a few meta tags to your HTML, including og:image, and social platforms use that image as a preview. The challenge isn't the protocol. It's producing the images themselves.
+
+
The traditional approaches all have significant drawbacks:
+
+
+
Manual design: Create each image by hand in Figma or Canva. Looks great, doesn't scale. You'll stop doing it after the fifth blog post.
+
Static templates: Use the same generic image for everything. Low effort, but also low engagement — every link looks identical.
+
Canvas/SVG generation: Write code to programmatically compose images using node-canvas, Sharp, or SVG rendering. Works, but you're essentially building a graphics engine. Text wrapping, font rendering, and layout calculations become your problem.
+
Self-hosted Puppeteer: Spin up a headless browser to screenshot an HTML template. Powerful, but now you're managing Chrome instances in production — the exact problem we discussed in our previous article.
+
+
+
Each approach trades off between quality, scalability, and engineering effort. What if you could get all three?
+
+
The Screenshot API Approach
+
+
The idea is elegant in its simplicity: design your OG image as an HTML page, then use a screenshot API to render it as a PNG. You get the full power of CSS for layout and styling, web fonts for typography, and zero infrastructure to maintain.
+
+
Here's the workflow:
+
+
+
Create an HTML template for your OG images (a simple page with your branding, title, and any dynamic content)
+
Host that template at a URL, passing page-specific data via query parameters
+
Call a screenshot API to capture it at 1200×630 pixels (the standard OG image size)
+
Set the resulting image URL as your og:image meta tag
+
+
+
The HTML Template
+
+
Your OG image template is just HTML and CSS. This means you can use flexbox for layout, Google Fonts for typography, gradients, shadows — anything the browser can render. Here's a minimal example:
That's it. The API launches a browser, renders your template, captures it at exactly 1200×630, and returns a pixel-perfect PNG. No Chrome processes to manage, no memory leaks to debug, no zombie browsers consuming your server's RAM.
+
+
Static vs. Dynamic Generation
+
+
There are two strategies for integrating OG images into your site, and the right choice depends on your content velocity and caching requirements.
+
+
Build-Time Generation (Static)
+
+
Generate all OG images during your build step and serve them as static files. This works well for blogs and documentation sites where content changes infrequently. Your CI pipeline calls the screenshot API for each page, saves the PNGs to your public directory, and deploys them alongside your HTML.
+
+
The advantage is zero runtime dependencies — your OG images are just static files on a CDN. The downside is that adding or updating content requires a rebuild.
+
+
On-Demand Generation (Dynamic)
+
+
Generate OG images on the fly when they're first requested, then cache them aggressively. This is ideal for sites with user-generated content, e-commerce product pages, or any scenario where you can't enumerate all pages at build time.
+
+
A typical implementation uses a serverless function or edge worker as a proxy:
With proper cache headers, each OG image is generated exactly once and then served from the CDN for subsequent requests. Your og:image tag simply points to /api/og-image?title=My+Page+Title.
+
+
Design Tips for Better OG Images
+
+
Since you're designing with HTML and CSS, you have enormous creative freedom. But social preview images have specific constraints worth respecting:
+
+
+
Size matters: Always render at 1200×630 pixels. This is the standard that works across Twitter, LinkedIn, Facebook, and Slack. Smaller images get upscaled and look blurry.
+
Keep text large: Your title should be at least 48px. Remember, these images are often displayed at small sizes on mobile feeds. If you can't read the text at 300px wide, it's too small.
+
Brand consistency: Include your logo and use your brand colors. OG images are branding real estate — make every share reinforce your visual identity.
+
Contrast is king: Social feeds are visually noisy. High contrast between text and background ensures your preview stands out. Dark backgrounds with light text tend to perform well.
+
Avoid text near edges: Some platforms crop OG images slightly. Keep all important content within 90% of the image area.
+
Test everywhere: Use tools like opengraph.xyz to preview how your OG images render across different platforms before shipping.
+
+
+
Performance Considerations
+
+
OG images don't need to be fast for end users — they're fetched by social platform crawlers, not by browsers loading your page. That said, there are performance aspects worth considering:
+
+
+
Cache aggressively: Set long Cache-Control headers. Social platforms cache OG images themselves, but your CDN should too. A week or more is typical.
+
Use PNG for text-heavy images: PNG preserves sharp text better than JPEG. The file sizes are larger, but since these images are fetched by crawlers (not loaded on every page view), it's an acceptable tradeoff.
+
Pre-generate when possible: For content you know about at build time, generate OG images during the build. Save the runtime generation for truly dynamic content.
+
Monitor your API usage: Each unique OG image is one API call. If you have 1,000 blog posts and rebuild nightly, that's 1,000 calls per day. Most screenshot APIs (including SnapAPI) offer generous free tiers and predictable per-screenshot pricing, but it's worth tracking.
+
+
+
Beyond Social Previews
+
+
Once you've set up the infrastructure for OG images, you'll find other uses for dynamically generated images. The same template approach works for:
+
+
+
Email headers: Personalized banner images for marketing emails
+
Certificate generation: Course completion certificates with the student's name rendered beautifully
+
Invoice previews: Thumbnail previews of invoices in dashboard UIs
+
Documentation screenshots: Auto-generated screenshots of your own UI for docs that stay current
+
+
+
The pattern is always the same: design it in HTML, screenshot it via API, serve the result. HTML becomes your universal image template language.
+
+
Conclusion
+
+
OG images shouldn't be an afterthought, and they shouldn't require a design team to maintain. By combining HTML templates with a screenshot API, you get the visual quality of hand-designed images with the scalability of automation. Every page gets a unique, branded social preview — automatically.
+
+
The best part? Your OG image templates are just code. They live in version control, they're easy to iterate on, and they update everywhere when you change your branding. No more stale Figma exports, no more missing preview images, no more sad text-only link shares.
+
+
Start with a simple template, wire it up to a screenshot API, add caching, and you're done. Your links will never look naked again.
+
+
+
Generate OG Images with SnapAPI
+
100 free screenshots to get started. Pixel-perfect rendering, EU-hosted, and ready in under a minute. Perfect for automated OG image generation.
How to Capture Dark Mode Screenshots Automatically
+
March 6, 20266 min read
+
+
Dark mode isn't a trend anymore — it's the default for most developers and a growing majority of users. Studies show that over 80% of developers work in dark mode, and roughly 70% of smartphone users have enabled it system-wide. If your app or website supports dark mode, you need to test, monitor, and preview both themes.
+
+
But capturing dark mode screenshots programmatically is surprisingly annoying. Until now.
+
+
The Problem: Dark Mode Is Harder Than It Looks
+
+
Websites implement dark mode in different ways. Some use the prefers-color-scheme CSS media query. Others toggle a class on the body element via JavaScript. Some use CSS custom properties that switch based on a data attribute. And some use all three simultaneously.
+
+
If you're running a headless browser to take screenshots, you need to handle all of these. With Puppeteer or Playwright, that means:
+
+
+
Emulating the prefers-color-scheme: dark media feature
+
Waiting for JavaScript theme toggles to apply
+
Handling race conditions where CSS transitions haven't finished
+
Managing different browser contexts for light vs. dark screenshots
+
+
+
That's a lot of boilerplate for what should be a simple toggle.
+
+
The Solution: One Parameter
+
+
SnapAPI's darkMode parameter handles all of this for you. Set it to true, and the browser emulates prefers-color-scheme: dark before loading the page. Any site that respects the system preference will render in dark mode automatically.
This is useful for generating consistent dark-themed previews across sites that don't natively support it.
+
+
Cleaning Up Screenshots
+
+
Real-world screenshots often include cookie banners, notification popups, and chat widgets that clutter the image. Use hideSelectors to remove them before capture:
Combined with dark mode, this gives you clean, professional screenshots every time.
+
+
Use Case: Dual OG Images for Social Media
+
+
One compelling use case is generating both light and dark Open Graph images for your website. Some platforms and apps display previews differently based on the user's system theme. By generating both variants, you can serve the right one via og:image based on context.
Screenshot API Performance: Caching Strategies That Actually Work
+
March 2, 20265 min read
+
+
Screenshot generation is inherently expensive. Every request launches a browser context, navigates to a URL, waits for rendering, and captures pixels. Without optimization, you're looking at 3-8 seconds per screenshot — unacceptable for any production API. Here's how we brought our p95 response times down from 6 seconds to under 600 milliseconds.
+
+
The Three Layers of Screenshot Caching
+
+
Effective caching for screenshot APIs operates at three distinct layers, each addressing a different performance bottleneck. Getting all three right is what separates a fast API from a sluggish one.
+
+
Layer 1: Content-Addressable Cache
+
+
The most impactful optimization is also the most straightforward: don't re-render screenshots you've already taken. We hash the request parameters — URL, viewport dimensions, format, device scale factor, and any custom options — into a cache key. If the same combination was requested recently, we serve the cached result directly.
+
+
The tricky part is cache invalidation. Web pages change, and serving a stale screenshot is worse than serving a slow one. Our approach uses configurable TTLs with smart defaults: 1 hour for most pages, shorter for known-dynamic content, and instant invalidation via a cache: false parameter when freshness is critical.
+
+
GET /v1/screenshot?url=example.com&cache_ttl=3600
+# First request: ~3s (full render)
+# Subsequent requests: ~50ms (cache hit)
+
+
This single layer eliminates 60-70% of all rendering work in a typical production workload, because many applications request the same URLs repeatedly — social preview generators, monitoring dashboards, and report builders all exhibit high cache hit ratios.
+
+
Layer 2: Browser Pool Management
+
+
For cache misses, the next bottleneck is browser startup time. Launching a fresh Chrome instance takes 1-2 seconds. Multiply that by concurrent requests and you have a performance disaster combined with memory pressure that triggers garbage collection storms.
+
+
Browser pooling solves this by maintaining a warm pool of ready-to-use browser contexts. Instead of launching Chrome per request, we allocate a pre-warmed context from the pool, navigate to the target URL, capture the screenshot, and return the context to the pool for reuse.
+
+
Key considerations for browser pool management:
+
+
+
Pool sizing: Too small and requests queue up. Too large and you waste memory. We dynamically scale based on request concurrency, maintaining a buffer of 2-3 idle contexts above current demand.
+
Context hygiene: Each context must be fully isolated — cleared cookies, fresh storage, no shared state. A screenshot of a banking login page shouldn't leak session data to the next request.
+
Health checks: Browser contexts degrade over time. Memory leaks accumulate. We cycle contexts after a configurable number of uses (default: 50) and immediately replace any that fail health checks.
+
Graceful degradation: When pool capacity is exhausted, we queue requests with backpressure rather than spawning unbounded Chrome instances. Better to return a slow response than to OOM the host.
+
+
+
Layer 3: CDN and Edge Delivery
+
+
Once a screenshot is generated, delivering it fast is a standard CDN problem — but with nuances specific to dynamically generated images. We push rendered screenshots to edge locations, so subsequent requests for the same content are served from the nearest point of presence.
+
+
For our EU-hosted infrastructure, this means edge nodes across European cities. A user in Berlin gets their screenshot from Frankfurt, not from the origin server in Nuremberg. The latency difference is 5-10ms versus 30-50ms — small in absolute terms, but it compounds when your API is called in a rendering pipeline.
+
+
Beyond Caching: Render Path Optimization
+
+
Caching handles repeated requests, but the cold-start path still needs to be fast. Several techniques reduce raw rendering time:
+
+
+
Eager navigation: Start loading the page before all parameters are validated. By the time we've checked auth and parsed options, the page is already halfway loaded.
+
Network idle detection: Instead of waiting a fixed duration, we monitor network activity and capture as soon as the page stabilizes. This adapts to fast-loading static sites (200ms) and heavy SPAs (2-3s) automatically.
+
Resource blocking: Optional blocking of ads, trackers, and analytics scripts that slow page loads without affecting visual output. This alone can save 500ms-1s on ad-heavy pages.
+
Viewport pre-configuration: Setting viewport dimensions before navigation avoids layout recalculation after the page loads, eliminating a common source of visual inconsistency and wasted time.
+
+
+
Measuring What Matters
+
+
Performance optimization without measurement is guesswork. The metrics that actually matter for a screenshot API are:
+
+
+
p50/p95/p99 response times — split by cache hit vs. miss. Your p95 cache-miss time is the number your users feel most.
+
Cache hit ratio — anything below 50% suggests your TTL strategy needs work or your traffic patterns are unusually diverse.
+
Pool utilization — consistently above 80% means you need more capacity. Consistently below 30% means you're wasting memory.
+
Error rate by type — timeouts, rendering failures, and OOM kills each have different root causes and different fixes.
+
+
+
We expose these metrics via a /health endpoint and internal dashboards, making it easy to spot performance regressions before they affect users.
+
+
Results
+
+
With all three caching layers active plus render path optimizations, our production numbers look like this: cache-hit responses average 45ms at p50 and 120ms at p95. Cache-miss responses average 1.8s at p50 and 2.9s at p95. The overall cache hit ratio sits at 72% across all customers, meaning nearly three-quarters of all screenshot requests are served without touching a browser.
+
+
For most API consumers, the performance feels instant — because for the majority of their requests, it is. That's the power of treating caching as a first-class architectural concern rather than an afterthought bolted on later.
+
+
+
Experience the Speed
+
Try SnapAPI's playground and see sub-second screenshot delivery in action. No signup required for your first request.
Why You Need a Screenshot API (And Why Building Your Own Is Harder Than You Think)
+
March 2, 20268 min read
+
+
Every developer has the same thought at some point: "I just need a screenshot of a webpage. How hard can it be?" You spin up a quick Node.js script with Puppeteer, take a screenshot, and it works. Ship it. Done. Right?
+
+
Not quite. What starts as a ten-line script inevitably grows into a sprawling system that consumes more engineering time than the feature it was supposed to support. Let's talk about why screenshot APIs exist, what makes them genuinely hard to build, and when it makes sense to use one instead of rolling your own.
+
+
The Puppeteer Trap
+
+
Puppeteer is an excellent tool. It gives you full control over a headless Chrome instance, and for local development or one-off scripts, it's perfect. The problem isn't Puppeteer itself — it's what happens when you try to run it in production at any meaningful scale.
+
+
Here's what your "simple screenshot service" needs to handle in the real world:
+
+
+
Resource management: Each Chrome instance consumes 200-500 MB of RAM. Running ten concurrent screenshots means you need several gigabytes of memory just for the browser processes. Without careful pooling, you'll OOM your server in hours.
+
Zombie processes: Chrome tabs crash. Pages hang on infinite loops. Scripts run forever. You need process monitoring, timeouts, and cleanup routines that kill orphaned processes before they eat your server alive.
+
Font rendering: That page looks perfect on your MacBook but renders with missing glyphs on your Ubuntu server. You need a curated font library — CJK fonts alone add hundreds of megabytes to your Docker image.
+
Network reliability: Target sites go down, return 503s, redirect endlessly, or serve CAPTCHAs. Your service needs retry logic, circuit breakers, and meaningful error reporting.
+
Security: Accepting arbitrary URLs means you're opening your server to SSRF attacks. Without proper validation, someone will use your screenshot service to probe your internal network, access cloud metadata endpoints, or worse.
+
+
+
Each of these is a project in itself. Together, they represent weeks of engineering work that has nothing to do with your actual product.
+
+
The Hidden Costs of Self-Hosting
+
+
Beyond the initial implementation, self-hosted screenshot services have ongoing costs that are easy to underestimate:
+
+
Infrastructure
+
Chrome is resource-hungry. A production screenshot service needs dedicated compute resources — you can't just tack it onto your existing application server. You're looking at dedicated containers or VMs with enough CPU and RAM to handle concurrent rendering, plus autoscaling if your traffic is bursty.
+
+
Maintenance
+
Chrome updates break things. Puppeteer version X works with Chrome version Y but not Z. Dependencies shift. Security patches need applying. Someone on your team needs to own this service, monitor it, and fix it when it breaks at 3 AM on a Saturday — because it will break at 3 AM on a Saturday.
+
+
Edge Cases
+
The long tail of web rendering is brutal. SPAs that need JavaScript execution. Pages with lazy-loaded content that require scroll simulation. Cookie consent banners that block the entire viewport. Dark mode detection. Viewport emulation for mobile screenshots. Each edge case is another conditional in your codebase, another test to maintain, another thing that can break.
+
+
+
"The first 80% of a screenshot service takes a weekend. The remaining 20% takes six months."
+
+
+
When a Screenshot API Makes Sense
+
+
A dedicated screenshot API isn't always the right choice. Here's a framework for deciding:
+
+
Use a screenshot API when:
+
+
Screenshots aren't your core product — they support a feature (social previews, PDF reports, monitoring dashboards)
+
You need reliability at scale without dedicating engineering resources to browser infrastructure
+
You need consistent rendering across different types of pages without handling every edge case yourself
+
Compliance matters — EU hosting, GDPR, data residency requirements are handled for you
+
You want to move fast and ship the actual feature instead of building plumbing
+
+
+
Build your own when:
+
+
Screenshots are your core product and you need deep customization
+
You're taking screenshots of your own application (not arbitrary URLs) in a controlled environment
+
Volume is very low (a few screenshots per day) and reliability isn't critical
+
You have specific security requirements that prevent using third-party services
+
+
+
What to Look for in a Screenshot API
+
+
Not all screenshot APIs are created equal. Here are the things that actually matter when evaluating options:
+
+
+
Rendering quality: Does it handle SPAs, web fonts, and modern CSS? Can it wait for dynamic content to load? The difference between a screenshot API that renders the loading spinner and one that waits for the actual content is everything.
+
Response time: Screenshot generation is inherently slow (browsers are complex), but good APIs use caching, browser pooling, and CDN delivery to minimize latency. Look for p95 response times under 3 seconds for cached content.
+
Output formats: PNG for quality, JPEG for size, WebP for the best of both. Full-page capture, viewport-only, or element-specific — flexibility matters when your use case evolves.
+
SSRF protection: Any API that accepts arbitrary URLs must validate them against internal network ranges, metadata endpoints, and redirect chains. This isn't optional — it's a security fundamental.
+
Data residency: If you're building for European users, your screenshots shouldn't be rendered on servers in Virginia. Look for APIs with explicit EU hosting and GDPR compliance documentation.
+
Transparent pricing: Per-screenshot pricing is the standard, but watch for hidden costs — bandwidth charges, storage fees, or premium features locked behind enterprise tiers.
+
+
+
The Build vs. Buy Calculation
+
+
Let's do the math. A senior developer costs roughly €80-120/hour fully loaded. Building a production-ready screenshot service takes 2-4 weeks minimum. That's €6,400-€19,200 in development costs alone, before ongoing maintenance.
+
+
A screenshot API at typical pricing (€9-79/month depending on volume) would need to run for years before matching the upfront development cost. And during those years, someone else handles the infrastructure, the Chrome updates, and the 3 AM incidents.
+
+
The calculation becomes even clearer when you factor in opportunity cost. Those 2-4 weeks of engineering time could go toward features that actually differentiate your product.
+
+
Conclusion
+
+
Screenshot APIs exist because taking screenshots of web pages at scale is a deceptively hard infrastructure problem. The initial implementation is easy; the production hardening is where the real work lives. For most teams, delegating this to a specialized service is the pragmatic choice — it lets you ship the feature your users actually care about instead of maintaining browser infrastructure.
+
+
The best engineering decisions aren't about what you can build. They're about what you should build. Save your complexity budget for the things that make your product unique.
+
+
+
Try SnapAPI Free
+
Get your first 100 screenshots free. No credit card required. EU-hosted, GDPR compliant, and ready in under a minute.
Every update, feature, and improvement to SnapAPI. Follow our progress as we build the best EU-hosted screenshot API.
+
+
+
+
+
+
+
+
+ v0.7.0
+ March 4, 2026
+ Latest
+
+
+
+
✨ New: css parameter — inject custom CSS into the page before capture (max 5000 chars). Perfect for custom fonts, color overrides, or complex layout adjustments
+
✨ New: darkMode parameter — emulate prefers-color-scheme: dark for dark mode screenshots
+
✨ New: hideSelectors parameter — hide elements by CSS selector before capture (max 10, 200 chars each)
Choosing the right screenshot API depends on your requirements — pricing, data residency, features, and developer experience. Here's an honest look at how the major screenshot APIs compare so you can pick the best fit for your project.
+
+
The Contenders
+
+
+
+
📸 SnapAPI
+
EU-hosted screenshot API with simple EUR pricing.
+
+
EU data residency (Germany)
+
POST & GET endpoints
+
Dark mode capture support
+
Element hiding (CSS selectors)
+
Built-in response caching
+
Free playground, no signup
+
Node.js & Python SDKs
+
Pricing in EUR
+
+
+
+
ScreenshotOne
+
Feature-rich API with global CDN.
+
+
Extensive rendering options
+
US-based infrastructure
+
USD pricing
+
+
+
+
URLBox
+
Established screenshot service with retina support.
+
+
Retina rendering
+
Webhook notifications
+
USD pricing
+
+
+
+
ApiFlash
+
Chrome-based screenshot API with CDN caching.
+
+
AWS-powered rendering
+
Built-in CDN
+
USD pricing
+
+
+
+
CaptureKit
+
Modern API with generous free tier.
+
+
Multiple output formats
+
Custom viewport sizes
+
USD pricing
+
+
+
+
GetScreenshot
+
Simple screenshot API for quick integrations.
+
+
Simple REST API
+
PNG & JPEG output
+
USD pricing
+
+
+
+
+
Why SnapAPI?
+
+
Every API on this list can take a screenshot. What sets SnapAPI apart is where and how it does it:
+
+
+
🇪🇺EU-hosted & GDPR compliant — All rendering happens on servers in Germany. Your data never leaves the EU. No extra DPAs or compliance headaches.
+
💶Simple EUR pricing — No currency conversion, no hidden fees. Plans start at €9/month with clear per-screenshot pricing.
+
🌙Dark mode capture — Take screenshots in dark mode with a single parameter. Perfect for showcasing dark themes and modern designs.
+
👁️🗨️Element hiding — Hide cookie banners, popups, and ads before capture using CSS selectors for clean results.
+
🔗GET & POST endpoints — Use GET requests to embed screenshots directly in <img> tags. No server-side code needed for simple use cases.
+
⚡Built-in caching — Response caching out of the box. Repeated requests for the same URL return cached results instantly.
+
🎮Free playground — Try the API in your browser without creating an account or entering payment details.
+
📦Official SDKs — First-class Node.js and Python SDKs to get you started in minutes.
+
+
+
+
Try SnapAPI Free
+
No signup required. Test screenshots in the playground, then get an API key when you're ready.
That's it — you'll have a screenshot.png file on your machine.
+
+
+
+
3
+
Use the GET endpoint for embedding
+
SnapAPI supports GET requests, which means you can embed screenshots directly in <img> tags — no server-side code needed:
+
+
+
HTML
+
<img src="https://snapapi.eu/v1/screenshot?url=https://example.com&format=png&apiKey=YOUR_API_KEY"
+ alt="Screenshot of example.com" />
+
+
+
This is perfect for dashboards, link previews, and documentation where you want live screenshots without any backend logic.
+
+
+
+
4
+
Use caching headers
+
SnapAPI includes built-in response caching. When you request the same URL multiple times, subsequent requests return the cached result instantly — saving you both time and credits.
+
Cache behavior is automatic. The Cache-Control headers in the response tell you the cache status.