DEV Community

Sohana Akbar
Sohana Akbar

Posted on

Dynamic Open Graph Images with AWS Lambda: A Complete Guide

Social media sharing can make or break your content's reach. When someone shares your link on platforms like Twitter, LinkedIn, or Facebook, that preview image is often the deciding factor between a click and a scroll-past. But generating unique, dynamic Open Graph images for every piece of content—without maintaining a complex infrastructure—is a challenge many developers face.

Enter AWS Lambda.

Why Dynamic OG Images Matter
Static Open Graph images work fine for blogs with a handful of posts, but they fall short when you have hundreds or thousands of dynamically generated pages. Think about e-commerce product pages, user-generated content, or personalized dashboards. Each URL deserves a unique preview that reflects its content—product images, user avatars, key metrics, or custom branding .

The traditional approach involves running a server with headless browsers (like Puppeteer) to render HTML templates and capture screenshots. This works, but it's expensive and requires constant maintenance. AWS Lambda offers a better way: serverless, pay-per-execution image generation that scales automatically.

The Architecture
Here's the high-level flow for generating dynamic OG images using AWS Lambda:

Trigger: A request comes in for an OG image (typically via a URL pattern like /api/og-image?title=...)

Lambda Execution: The function generates an HTML template with dynamic content

Rendering: Using a headless browser (Puppeteer or Playwright) within Lambda, the HTML is rendered and captured as an image

Storage: The image is uploaded to S3 (with proper cache headers)

Response: A redirect to the S3 image URL is returned

Getting Started: The Tech Stack
Core Components
AWS Lambda: Serverless compute for generating images

Puppeteer or Playwright: Headless browser for HTML-to-image conversion

AWS S3: Storage for generated images with CDN integration

Amazon CloudFront: Optional but recommended for caching and fast delivery

AWS API Gateway: HTTP endpoint for triggering generation

Lambda Configuration Considerations
The Lambda function requires specific configuration:

yaml
Runtime: Node.js 20.x (or Python 3.12)
Memory: 1024-2048 MB (critical for headless browser performance)
Timeout: 30-60 seconds (allowing time for rendering)
Ephemeral Storage: 512 MB (for browser executable and temporary files)
Layer Setup for Puppeteer
For Node.js, the easiest approach is using the @sparticuz/chromium package, which provides a pre-built Chromium binary compatible with Lambda:

javascript
const chromium = require('@sparticuz/chromium');
const puppeteer = require('puppeteer-core');

exports.handler = async (event) => {
const browser = await puppeteer.launch({
args: chromium.args,
defaultViewport: chromium.defaultViewport,
executablePath: await chromium.executablePath(),
headless: chromium.headless,
});

// ... rendering logic
};
For Python developers, chrome-aws-lambda provides similar functionality.

Building the Image Generator
Step 1: Create an HTML Template
Define your OG image template using HTML and CSS. This gives you complete control over typography, colors, and layout:

javascript
function generateHTML(title, subtitle, author, category) {
return
<!DOCTYPE html>
<html>
<head>
<style>
body {
width: 1200px;
height: 630px;
margin: 0;
display: flex;
align-items: center;
justify-content: center;
font-family: 'Inter', -apple-system, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 60px;
}
.content {
text-align: center;
}
.category {
font-size: 24px;
text-transform: uppercase;
letter-spacing: 4px;
opacity: 0.8;
margin-bottom: 20px;
}
.title {
font-size: 56px;
font-weight: 800;
line-height: 1.2;
margin-bottom: 20px;
}
.subtitle {
font-size: 28px;
opacity: 0.9;
}
.author {
margin-top: 30px;
font-size: 20px;
opacity: 0.7;
}
</style>
</head>
<body>
<div class="content">
<div class="category">${category}</div>
<div class="title">${title}</div>
<div class="subtitle">${subtitle}</div>
<div class="author">by ${author}</div>
</div>
</body>
</html>
;
}
Step 2: Render and Capture
The Lambda function renders the HTML and captures it as a PNG:

javascript
const page = await browser.newPage();
await page.setContent(htmlContent, { waitUntil: 'networkidle0' });
const screenshot = await page.screenshot({ type: 'png' });
await browser.close();
Step 3: Upload to S3 and Cache
Store the generated image with a unique key based on content parameters:

javascript
const s3Key = og-images/${hashContent(params)}.png;

await s3.putObject({
Bucket: process.env.IMAGE_BUCKET,
Key: s3Key,
Body: screenshot,
ContentType: 'image/png',
CacheControl: 'public, max-age=31536000', // Cache for a year
}).promise();

return {
statusCode: 302,
headers: { Location: https://cdn.example.com/${s3Key} }
};
Performance Optimizations

  1. Cache Everything
    Generate each image once and cache it indefinitely. Use a hash of your content parameters as the filename so identical content always returns the same cached image.

  2. Use Provisioned Concurrency
    Lambda cold starts can be particularly painful when loading Chromium. Provisioned concurrency keeps your function warm and reduces latency.

  3. Consider Lambda Function URLs
    For simpler setups, API Gateway isn't strictly necessary. Lambda Function URLs provide a direct HTTP endpoint with built-in caching support.

  4. Implement Tiered Caching
    If using CloudFront, set up behavior rules to cache OG images at the edge. This reduces Lambda invocations to near-zero for popular content.

Common Pitfalls and Solutions
Cold Start Performance: The first invocation after inactivity can take 3-5 seconds. Use provisioned concurrency or a CloudFront edge function to warm your function periodically.

Memory Limits: Chromium is memory-hungry. Start with 1024 MB and monitor. If your images are complex, you might need 2048 MB.

Timeout Issues: Rendering can be slow. Set your Lambda timeout to at least 30 seconds, and monitor execution times to adjust accordingly.

HTML Complexity: The simpler your HTML template, the faster the rendering. Avoid complex JavaScript or external font loading if possible.

When to Use This Pattern
Dynamic OG image generation with Lambda shines when:

You have many unique pages (100+)

Content changes frequently

Branding requires consistent design

You want to A/B test different image styles

You need personalized previews (user avatars, names, etc.)

However, for small blogs or static sites, pre-generating images at build time using a CI/CD pipeline might be simpler and cheaper.

Beyond OG Images
This same architecture can be extended for:

Social media share cards for Twitter/X, LinkedIn, Facebook

Dynamic PDF generation for reports and invoices

Screenshot services

Programmatic image creation for newsletters or email campaigns

The combination of AWS Lambda, Puppeteer, and S3 creates a powerful, cost-effective system for generating visuals on demand. With proper caching, you can handle millions of shares with minimal cost and zero infrastructure management.

Top comments (0)