Skip to content

React Integration

Generate OG images for React applications (Create React App, Vite, etc.)

Overview

React SPAs need server-side OG image generation for social sharing. Options:

  1. Build-time generation — Generate images during build
  2. Server/serverless function — Generate on-demand
  3. Signed URLs — Direct links with no server needed

Build-Time Generation

Best for content that doesn't change often.

Setup

bash
npm install node-fetch

Build Script

javascript
// scripts/generate-og.js
const fs = require('fs');
const path = require('path');

const pages = [
  { slug: 'home', title: 'Welcome to MyApp', subtitle: 'Build amazing things' },
  { slug: 'about', title: 'About Us', subtitle: 'Our mission and team' },
  { slug: 'contact', title: 'Contact', subtitle: 'Get in touch' }
];

async function generateOGImages() {
  const outputDir = path.join(__dirname, '../public/og');
  
  if (!fs.existsSync(outputDir)) {
    fs.mkdirSync(outputDir, { recursive: true });
  }
  
  for (const page of pages) {
    const response = await fetch('https://ogimageapi.io/api/generate', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-API-Key': process.env.REACT_APP_OG_IMAGE_API_KEY
      },
      body: JSON.stringify({
        title: page.title,
        subtitle: page.subtitle,
        template: 'default',
        theme: 'dark'
      })
    });
    
    const buffer = await response.arrayBuffer();
    fs.writeFileSync(
      path.join(outputDir, `${page.slug}.png`),
      Buffer.from(buffer)
    );
    
    console.log(`✓ Generated ${page.slug}.png`);
  }
}

generateOGImages().catch(console.error);

Package.json

json
{
  "scripts": {
    "generate-og": "node scripts/generate-og.js",
    "build": "npm run generate-og && react-scripts build"
  }
}

Using react-helmet

javascript
// components/SEO.jsx
import { Helmet } from 'react-helmet';

export function SEO({ title, description, image }) {
  const ogImage = image || `/og/default.png`;
  
  return (
    <Helmet>
      <title>{title}</title>
      <meta name="description" content={description} />
      
      <meta property="og:title" content={title} />
      <meta property="og:description" content={description} />
      <meta property="og:image" content={ogImage} />
      <meta property="og:type" content="website" />
      
      <meta name="twitter:card" content="summary_large_image" />
      <meta name="twitter:title" content={title} />
      <meta name="twitter:description" content={description} />
      <meta name="twitter:image" content={ogImage} />
    </Helmet>
  );
}

Usage

javascript
// pages/BlogPost.jsx
import { SEO } from '../components/SEO';

export function BlogPost({ post }) {
  return (
    <>
      <SEO
        title={post.title}
        description={post.excerpt}
        image={`/og/blog-${post.slug}.png`}
      />
      <article>
        {/* Post content */}
      </article>
    </>
  );
}

Serverless Function (AWS Lambda)

For dynamic content, use a serverless function.

Lambda Handler

javascript
// lambda/og-image.js
const https = require('https');

exports.handler = async (event) => {
  const { title, subtitle, template } = event.queryStringParameters || {};
  
  const requestBody = JSON.stringify({
    title: title || 'My App',
    subtitle: subtitle || '',
    template: template || 'default',
    theme: 'dark'
  });
  
  return new Promise((resolve, reject) => {
    const req = https.request({
      hostname: 'ogimageapi.io',
      path: '/api/generate',
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-API-Key': process.env.OG_IMAGE_API_KEY,
        'Content-Length': Buffer.byteLength(requestBody)
      }
    }, (res) => {
      const chunks = [];
      
      res.on('data', (chunk) => chunks.push(chunk));
      res.on('end', () => {
        const buffer = Buffer.concat(chunks);
        
        resolve({
          statusCode: 200,
          headers: {
            'Content-Type': 'image/png',
            'Cache-Control': 'public, max-age=86400'
          },
          body: buffer.toString('base64'),
          isBase64Encoded: true
        });
      });
    });
    
    req.on('error', reject);
    req.write(requestBody);
    req.end();
  });
};

Cloudflare Workers

javascript
// workers/og-image.js
export default {
  async fetch(request, env) {
    const url = new URL(request.url);
    const title = url.searchParams.get('title') || 'My App';
    const subtitle = url.searchParams.get('subtitle') || '';
    
    const response = await fetch('https://ogimageapi.io/api/generate', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-API-Key': env.OG_IMAGE_API_KEY
      },
      body: JSON.stringify({
        title,
        subtitle,
        template: 'default',
        theme: 'dark'
      })
    });
    
    return new Response(await response.arrayBuffer(), {
      headers: {
        'Content-Type': 'image/png',
        'Cache-Control': 'public, max-age=86400'
      }
    });
  }
};

Using Signed URLs

Simplest approach — no server needed.

javascript
// utils/og.js
export async function getSignedOGUrl(params) {
  const queryString = new URLSearchParams(params).toString();
  
  const response = await fetch(
    `https://ogimageapi.io/api/signed/generate?${queryString}`,
    {
      headers: {
        'X-API-Key': process.env.REACT_APP_OG_IMAGE_API_KEY
      }
    }
  );
  
  const data = await response.json();
  return data.url;
}
javascript
// During build or SSR
const ogUrl = await getSignedOGUrl({
  title: 'My Page Title',
  subtitle: 'Description here',
  template: 'default',
  expires_in: 604800 // 7 days
});

// Use in meta tags
<meta property="og:image" content={ogUrl} />

Vite Configuration

javascript
// vite.config.js
import { defineConfig, loadEnv } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig(({ mode }) => {
  const env = loadEnv(mode, process.cwd(), '');
  
  return {
    plugins: [react()],
    define: {
      'process.env.OG_IMAGE_API_KEY': JSON.stringify(env.OG_IMAGE_API_KEY)
    }
  };
});

Best Practices

  1. Never expose API key in client code — Use build scripts or serverless
  2. Generate at build time when possible
  3. Cache aggressively — Images rarely change
  4. Use signed URLs for simplest setup
  5. Have fallback images for errors

Generate stunning Open Graph images programmatically.