DEV Community

Boehner
Boehner

Posted on

Generate Images from HTML with One API Call (No Puppeteer Required)

Every few months I find myself needing to generate images from HTML — OG cards for a blog, email template previews, dashboard exports, report cover pages. And every time, the path is the same: set up Puppeteer, configure it for headless, handle the Docker sandbox issues, write the render logic, deal with a crash at 2am.

There's a simpler way.

What the render endpoint does

SnapAPI's /v1/render endpoint accepts raw HTML and returns a pixel-perfect PNG, JPEG, or WebP. One POST, one image. No browser to install, no sandbox flags, no cold-start.

curl -X POST 'https://snapapi.tech/v1/render' \
  -H 'x-api-key: YOUR_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "html": "<div style=\"background:#0f172a;color:#22c55e;padding:80px;font-size:48px;font-family:sans-serif\">Hello, world</div>",
    "width": 1200,
    "height": 630,
    "format": "png"
  }' \
  --output card.png
Enter fullscreen mode Exit fullscreen mode

That's the complete workflow.

Three real use cases

1. Dynamic OG card generation

Instead of uploading a static image for every blog post, generate one at publish time using the post's title and description:

const SnapAPI = require('snapapi-sdk');
const fs = require('fs');
const client = new SnapAPI(); // reads SNAPAPI_KEY from env

async function generateOGCard(post) {
  const html = `
    <!DOCTYPE html><html><head><style>
      * { margin:0; padding:0; box-sizing:border-box; }
      body {
        width:1200px; height:630px;
        background:#0f172a; color:#f1f5f9;
        font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;
        display:flex; flex-direction:column;
        justify-content:center; padding:80px;
      }
      h1 { font-size:52px; font-weight:800; margin-bottom:20px; line-height:1.15; }
      p  { font-size:24px; color:#94a3b8; }
      .brand { position:absolute; bottom:40px; right:60px; font-size:16px; color:#475569; }
    </style></head>
    <body>
      <h1>${post.title}</h1>
      <p>${post.description.slice(0, 120)}</p>
      <span class="brand">yourblog.com</span>
    </body></html>`;

  const png = await client.render(html, { width: 1200, height: 630 });
  fs.writeFileSync(`og/${post.slug}.png`, png);
  return `https://yourblog.com/og/${post.slug}.png`;
}
Enter fullscreen mode Exit fullscreen mode

Run this at publish time. Every post gets a correctly sized, on-brand OG image without touching Figma or Canva.

2. Email template preview rendering

Before sending a campaign, render each template variant to PNG and review them as images:

async function previewEmailTemplate(htmlTemplate) {
  // Render at 600px wide (standard email width)
  const png = await client.render(htmlTemplate, { width: 600, height: 400 });
  fs.writeFileSync('email-preview.png', png);
  console.log('Preview saved — open email-preview.png to review');
}
Enter fullscreen mode Exit fullscreen mode

No email client setup. No browser. Just an image you can review or attach to a Slack message for approval.

3. Dashboard export for reports

Render a data visualization as an image for a PDF report or Slack digest:

const chartHtml = `
  <div style="background:white;padding:40px;width:800px;font-family:sans-serif">
    <h2 style="margin-bottom:20px">Weekly Signups</h2>
    <div style="display:flex;align-items:flex-end;gap:8px;height:200px">
      ${[42, 58, 35, 71, 89, 63, 94].map(v =>
        `<div style="background:#22c55e;width:40px;height:${v * 2}px;border-radius:4px 4px 0 0"></div>`
      ).join('')}
    </div>
    <div style="display:flex;gap:8px;margin-top:8px;font-size:12px;color:#64748b">
      ${['Mon','Tue','Wed','Thu','Fri','Sat','Sun'].map(d =>
        `<div style="width:40px;text-align:center">${d}</div>`
      ).join('')}
    </div>
  </div>`;

const chartPng = await client.render(chartHtml, { width: 800, height: 320 });
fs.writeFileSync('weekly-chart.png', chartPng);
Enter fullscreen mode Exit fullscreen mode

The image drops straight into a PDF, email, or Slack attachment.

The three OG card gotchas

If you've tried this before and got a broken image, it's usually one of:

  1. Web fonts don't load@import url(...) won't work in a server-render context. Use system fonts or base64-encode your font directly in the HTML.
  2. Relative paths fail — any src="/images/..." will 404. All URLs must be absolute.
  3. Dimensions in the HTML must match the API call — if you request width:1200,height:630 but the root element in your HTML is only 800px wide, you'll get padding or cropping. Set the root element dimensions explicitly in CSS.

Install

npm install snapapi-sdk
Enter fullscreen mode Exit fullscreen mode
const SnapAPI = require('snapapi-sdk');
const client = new SnapAPI(); // SNAPAPI_KEY from env
const png = await client.render(html, { width: 1200, height: 630 });
Enter fullscreen mode Exit fullscreen mode

Full reference: snapapi.tech/render-api

Free API key

100 renders/month · No credit card · Active in 30 seconds

snapapi.tech/start

Top comments (0)