Skip to content

Instantly share code, notes, and snippets.

@Saad5400
Created July 13, 2025 01:04
Show Gist options
  • Save Saad5400/a8ce37f0c294906ca7cd421ec4798471 to your computer and use it in GitHub Desktop.
Save Saad5400/a8ce37f0c294906ca7cd421ec4798471 to your computer and use it in GitHub Desktop.
Nuxt Serverless function to generate OG Image using puppeteer (Supports Arabic and RTL)
import chromium from "@sparticuz/chromium-min";
import puppeteerCore, { Browser, Page } from "puppeteer-core";
// -----------------------------------------------------------------------------
// CONFIGURATION & CONSTANTS
// -----------------------------------------------------------------------------
// URL to download your Chromium build; swap this for your own custom build or version
// Tip: You can host your own build on S3 or another CDN for faster startup
const remoteExecutablePath =
"https://github.com/Sparticuz/chromium/releases/download/v138.0.1/chromium-v138.0.1-pack.x64.tar";
// Cache duration in seconds (here, 8 hours)
// Tip: Adjust based on how dynamic your underlying pages are; shorter for frequently changing content
const cache = 60 * 60 * 8;
// Singletons for browser and page to improve performance
let browser: Browser | null = null;
let page: Page | null = null;
// Default screenshot dimensions (1.91:1 aspect ratio)
// Tip: Change these or accept additional query params for custom aspect ratios
const DEFAULT_WIDTH = 720;
const DEFAULT_HEIGHT = 377;
// -----------------------------------------------------------------------------
// MAIN SCREENSHOT HANDLER
// -----------------------------------------------------------------------------
/**
* Handles incoming requests and returns a PNG screenshot of the target URL.
*
* @param event - Request event object (contains query params, headers, etc.)
* @returns Response containing the PNG buffer and HTTP headers
*/
async function screenshotHandler(event: any) {
// -------------------------------------------------------------------------
// PARSE & VALIDATE INPUT
// -------------------------------------------------------------------------
// Extract `path`, `width`, and `height` from query parameters
// Tip: Extend getQuery to support other params like `format=jpeg` or `delay`
const { path, width: wQ, height: hQ } = getQuery(event);
if (!path) {
// Return a 400 error if `path` is missing
throw createError({ statusCode: 400, statusMessage: "path parameter is required" });
}
// Coerce width/height or fallback to defaults
const width = wQ ? parseInt(wQ as string, 10) : DEFAULT_WIDTH;
const height = hQ ? parseInt(hQ as string, 10) : DEFAULT_HEIGHT;
// -------------------------------------------------------------------------
// LAUNCH BROWSER & SETUP PAGE
// -------------------------------------------------------------------------
// Lazily launch and cache the browser instance
if (!browser) {
browser = await puppeteerCore.launch({
args: chromium.args,
// In DEV, use system-installed Chromium; otherwise download remote build
executablePath: process.env.DEV
? "/usr/bin/chromium"
: await chromium.executablePath(remoteExecutablePath),
headless: true, // Run in headless mode
});
}
// Lazily instantiate a new page for speed
if (!page) {
page = await browser.newPage();
}
// -------------------------------------------------------------------------
// NAVIGATE & RENDER
// -------------------------------------------------------------------------
// Construct the full URL; replace the base URL to match your own domain
// Tip: You can derive `baseUrl` dynamically from headers or config
const url = `https://uqucc.sb.sa${path}`;
await page.goto(url);
// Set viewport with high DPI for crisp images
await page.setViewport({ width, height, deviceScaleFactor: 2 });
// Optional page tweaks: hide scrollbars or remove headers
await page.evaluate(() => {
// Ensure no layout shift due to scrollbars
document.documentElement.style.scrollbarGutter = "auto";
// Remove the <header> element if present to focus on content
document.querySelector("header")?.remove();
});
// Capture screenshot as Buffer; can pass options to clip or change format
// Tip: Use `await page.screenshot({ clip: {...} })` to capture a specific region
const buffer = await page.screenshot();
// -------------------------------------------------------------------------
// PREPARE RESPONSE
// -------------------------------------------------------------------------
// Set response headers; include caching in prod for CDN efficiency
const headers: Record<string, string> = { "Content-Type": "image/png" };
if (!process.env.DEV) {
headers["Cache-Control"] = `public, max-age=${cache}`;
}
return new Response(buffer, { headers });
}
// -----------------------------------------------------------------------------
// EXPORT HANDLER
// -----------------------------------------------------------------------------
// In DEV mode, return raw handler for local feedback; in prod, use cached wrapper
// Tip: `defineCachedEventHandler` is Nitro-specific—swap out for your own framework if needed
export default process.env.DEV
? defineEventHandler(screenshotHandler)
: defineCachedEventHandler(screenshotHandler, { maxAge: cache });
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment