DEV Community

Tamiz Uddin
Tamiz Uddin

Posted on Originally published at tamiz.pro

Build Once, Render Anywhere: Generating OG Images and PDFs from JSX/HTML Without a Headless Browser Using Rust-Powered Takumi

Originally published on tamiz.pro.

Generating Open Graph (OG) images and PDFs from JSX or HTML is a common need for dynamic social sharing, reports, and invoices. The typical approach—spinning up a headless browser like Puppeteer or Playwright—is heavy: high memory usage, slow cold starts, and complex orchestration. Takumi offers a radical alternative: a Rust-powered renderer that converts JSX/HTML directly into images and PDFs, without a browser. In this tutorial, you'll learn how to build a reusable rendering service that takes JSX/HTML as input and outputs PNG or PDF, using Takumi's Node.js bindings. You'll see how to set up the environment, write render functions, handle dynamic data, and deploy efficiently. By the end, you'll have a production-ready pattern that cuts rendering latency and resource consumption dramatically.

Table of Contents

1. Why Takumi?

Headless browsers are powerful but overkill for static rendering. They consume 200–500 MB of RAM per instance)Skip to content

Takumi is a Rust-based rendering engine that interprets a subset of HTML/CSS and JSX, rasterizing to images or vector PDFs. It's designed for serverless environments and edge functions where cold starts matter. Key benefits:

  • Memory footprint: Under 50 MB per instance, compared to 300+ MB for Chromium.
  • Startup time: ~10ms to initialize, vs 300ms+ for a browser launch.
  • Deterministic output: No layout flakiness; pixel-perfect results.
  • Language agnostic: Use from Node.js, Python, or directly via HTTP.

Takumi does not execute JavaScript. That's a feature: it forces you to precompute dynamic values and pass them as props, resulting in predictable and secure rendering.

2. Prerequisites

  • Node.js 18+ (we'll use the official @takumi-js/core package)
  • npm or yarn
  • Basic familiarity with JSX (we'll use React-like syntax, but Takumi has its own runtime)
  • No need for a browser installation

3. Setting Up the Project

Create a new directory and initialize:

mkdir og-renderer
cd og-renderer
npm init -y
npm install @takumi-js/core
Enter fullscreen mode Exit fullscreen mode

Takumi's Node package wraps the native Rust binary. For serverless, you can also use the standalone HTTP server (we'll cover that later).

Now create a basic index.js file and verify the installation:

const { renderToImage } = require('@takumi-js/core');

console.log('Takumi loaded', typeof renderToImage);
Enter fullscreen mode Exit fullscreen mode

Run it:

node index.js
Enter fullscreen mode Exit fullscreen mode

You should see Takumi loaded function.

4. Core Rendering API

Takumi exposes two primary functions:

  • renderToImage(jsx, options) → returns a Buffer (PNG or JPEG)
  • renderToPdf(html, options) → returns a Buffer (PDF)

Both accept a string of JSX/HTML and an options object for width, height, format, and quality.

The JSX is not compiled by Babel; it's parsed by Takumi's own parser. You can write JSX-like syntax directly in a template literal, or use a .tsx file and compile it to a string before passing.

Options overview

Option Type Default Description
width number 1200 Output width in pixels
height number 630 Output height (for images)
format string 'png' 'png' or 'jpeg'
quality number 90 JPEG quality only
pdf object {} PDF-specific options (page size, margins)

5. Rendering an OG Image from JSX

Let's create a function that takes a title, subtitle, and brand color, and returns a PNG Buffer for an OG image.

Create render-og.js:

const { renderToImage } = require('@takumi-js/core');

function generateOgImage({ title, subtitle, bgColor = '#1a202c', textColor = '#ffffff' }) {
  const jsx = `
    <div style="display: flex; flex-direction: column; justify-content: center; align-items: center; width: 100%; height: 100%; background: ${bgColor}; padding: 40px;">
      <h1 style="font-size: 64px; color: ${textColor}; margin-bottom: 16px;">${title}</h1>
      <p style="font-size: 32px; color: rgba(255,255,255,0.8);">${subtitle}</p>
    </div>
  `;

  return renderToImage(jsx, {
    width: 1200,
    height: 630,
    format: 'png'
  });
}

// Example usage
const fs = require('fs');
const buffer = generateOgImage({
  title: 'Build Once, Render Anywhere',
  subtitle: 'Rust-powered rendering without a browser'
});

fs.writeFileSync('og-image.png', buffer);
console.log('OG image generated');
Enter fullscreen mode Exit fullscreen mode

Run it:

node render-og.js
Enter fullscreen mode Exit fullscreen mode

You should get a clean 1200x630 PNG. Notice we used inline styles—Takumi supports a subset of CSS, including flexbox, which is enough for most layouts.

6. Rendering a PDF from HTML

For PDFs, you can pass HTML (or JSX) and specify PDF options. Here's how to generate a simple invoice PDF:

Create render-pdf.js:

const { renderToPdf } = require('@takumi-js/core');

function generateInvoicePdf({ customerName, amount, invoiceId }) {
  const html = `
    <html>
      <body style="font-family: Arial, sans-serif; padding: 40px;">
        <h1 style="color: #333;">Invoice #${invoiceId}</h1>
        <p>Customer: <strong>${customerName}</strong></p>
        <p>Amount due: <strong>$${amount}</strong></p>
        <p>Thank you for your business!</p>
      </body>
    </html>
  `;

  return renderToPdf(html, {
    pdf: {
      pageSize: 'A4',
      margins: { top: '20mm', bottom: '20mm', left: '20mm', right: '20mm' }
    }
  });
}

const fs = require('fs');
const buffer = generateInvoicePdf({
  customerName: 'Jane Doe',
  amount: '199.99',
  invoiceId: 'INV-2025-001'
});

fs.writeFileSync('invoice.pdf', buffer);
console.log('PDF generated');
Enter fullscreen mode Exit fullscreen mode

Run it:

node render-pdf.js
Enter fullscreen mode Exit fullscreen mode

Open the PDF to see a clean, vector-based document. Takumi's PDF output is vector, meaning text is selectable and sharp at any zoom.

7. Handling Dynamic Data and Styling

In a real application, you'll have dynamic data from a database or API. Since Takumi doesn't run JS, you must precompute values and inject them into the template. Here's a pattern using a template function:

function renderBlogCard(post) {
  const jsx = `
    <div style="display: flex; height: 100%; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 40px;">
      <div style="display: flex; flex-direction: column; justify-content: space-between; width: 100%;">
        <div>
          <p style="font-size: 24px; color: rgba(255,255,255,0.8); text-transform: uppercase;">${post.category}</p>
          <h1 style="font-size: 48px; color: white; margin-top: 12px;">${post.title}</h1>
        </div>
        <div style="display: flex; align-items: center;">
          <img src="${post.authorAvatar}" style="width: 48px; height: 48px; border-radius: 50%;" />
          <p style="font-size: 20px; color: white; margin-left: 12px;">${post.author}</p>
        </div>
      </div>
    </div>
  `;
  return jsx;
}

// Usage
const post = {
  category: 'Engineering',
  title: 'Why We Ditched Headless Browsers',
  author: 'Alex Rivera',
  authorAvatar: 'https://example.com/alex.png'
};

renderToImage(renderBlogCard(post), { width: 1200, height: 630 })
  .then(buf => fs.writeFileSync('blog-card.png', buf));
Enter fullscreen mode Exit fullscreen mode

Styling limitations

Takumi supports a useful subset of CSS: flexbox, grid (basic), colors, fonts (system and custom via @font-face), borders, border-radius, box-shadow, and text styling. It does not support position: absolute or complex animations. For most OG images and simple PDFs, this is sufficient.

To use custom fonts, you can embed them as base64 in a <style> tag:

const fontBase64 = '...'; // read from file
const jsx = `
  <style>
    @font-face {
      font-family: 'Inter';
      src: url(data:font/ttf;base64,${fontBase64}) format('truetype');
    }
  </style>
  <div style="font-family: 'Inter', sans-serif;">...</div>
`;
Enter fullscreen mode Exit fullscreen mode

8. Performance and Deployment Considerations

Memory and CPU

Takumi uses a single native thread and is non-blocking in Node. It's ideal for serverless functions (AWS Lambda, Vercel Edge, Cloudflare Workers). You can run it in a warm container and render in under 5ms for a typical OG image.

Caching

Since output is deterministic for given inputs, cache aggressively. Use a key based on the template + data hash:

const crypto = require('crypto');
function cacheKey(data) {
  return crypto.createHash('sha256').update(JSON.stringify(data)).digest('hex');
}
// In a serverless function, use a CDN or in-memory cache.
Enter fullscreen mode Exit fullscreen mode

HTTP server mode

For microservices, Takumi can run as a standalone HTTP server. The Node package includes a serve function:

const { serve } = require('@takumi-js/core');
serve(3000); // starts a REST API
Enter fullscreen mode Exit fullscreen mode

Then POST to /render with JSON body:

{
  "jsx": "<div>Hello</div>",
  "options": { "width": 800, "height": 400, "format": "png" }
}
Enter fullscreen mode Exit fullscreen mode

This decouples rendering from your app's runtime.

Cold start comparison

Approach Cold start Memory per instance Throughput (images/sec)
Puppeteer 300-800ms 300-500 MB ~10
Takumi 10-20ms 40-60 MB ~100

Numbers are indicative; your mileage may vary.

9. Frequently Asked Questions

Can I use Takumi with React components?

Yes, but you need to compile your React component to a string of JSX. You can use renderToString from ReactDOMServer (which outputs HTML) and then pass that HTML to Takumi, or you can use a custom Babel plugin to output JSX text. However, Takumi's JSX parser is not a full React implementation—it only supports declarative elements, not hooks or state. For static content, it works well.

Does Takumi support images from URLs?

Yes, you can use <img src="https://...">. Takumi will fetch the image at render time. Ensure the URL is accessible from your runtime environment. For performance, consider pre-downloading images and using base64 data URIs.

Is Takumi open source?

Takumi is available under a commercial license, but there is a free tier for low-volume usage. Check the official Takumi website for the latest pricing and terms.


By replacing headless browsers with Takumi, you can achieve dramatic improvements in speed and resource usage for static rendering tasks. Start integrating it into your existing stack today—your serverless bill and your users' latency will thank you.

Top comments (0)