OG Image Generation for React Apps
Generate og:image with React using the FastOG API — sign up free for 100 credits (create your API key in the dashboard), credit-based, HMAC-secured. This guide shows you how to create a signer utility for HMAC-signed OG image URLs and render dynamic Open Graph images on the fly. Try the Free OG Image Tester to preview your design before you ship.
Quick Start
1. Create the signer utility
// src/utils/og-image-signer.js
export function generateSignedOgUrl(params, options = {}) {
const {
baseUrl = "https://fastog.com/api/v1/og",
apiKey = import.meta.env.VITE_FASTOG_API_KEY || process.env.REACT_APP_FASTOG_API_KEY,
hmacSecret = import.meta.env.VITE_FASTOG_HMAC_SECRET || process.env.REACT_APP_FASTOG_HMAC_SECRET,
} = options;
if (!apiKey || !hmacSecret) {
throw new Error("Missing FASTOG_API_KEY or FASTOG_HMAC_SECRET");
}
const timestamp = Math.floor(Date.now() / 1000);
const url = new URL(baseUrl);
for (const [key, value] of Object.entries(params)) {
url.searchParams.set(key, value);
}
const apiKey = apiKey;
const path = url.pathname;
const message = `GET${path}${timestamp}${apiKey}`;
const signature = await crypto.subtle.importKey(
"raw",
new TextEncoder().encode(hmacSecret),
{ name: "HMAC", hash: "SHA-256" },
false,
["sign"]
).then(async (key) => {
const signature = await crypto.subtle.sign(
"HMAC",
key,
new TextEncoder().encode(message)
);
return Array.from(new Uint8Array(signature))
.map(b => b.toString(16).padStart(2, "0"))
.join("");
});
url.searchParams.set("key", apiKey);
url.searchParams.set("s", signature);
url.searchParams.set("t", timestamp);
return url.toString();
}⚠️ Important: In React SPAs, signing happens client-side. For production, move signing to a backend API route to keep the HMAC secret secure.
Backend API Route (Recommended)
Express Backend
// server/routes/og.js
import express from "express";
import crypto from "crypto";
const router = express.Router();
router.get("/api/og", (req, res) => {
const { title, template = "default", ...params } = req.query;
if (!title) {
return res.status(400).json({ error: "Missing title" });
}
const timestamp = Math.floor(Date.now() / 1000);
const apiKey = process.env.FASTOG_API_KEY;
const hmacSecret = process.env.FASTOG_HMAC_SECRET;
const apiKey = apiKey;
const message = `GET/api/v1/og${timestamp}${apiKey}`;
const signature = crypto
.createHmac("sha256", hmacSecret)
.update(message)
.digest("hex");
const url = new URL("https://fastog.com/api/v1/og");
url.searchParams.set("template", template);
url.searchParams.set("title", title);
Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v));
url.searchParams.set("key", apiKey);
url.searchParams.set("s", signature);
url.searchParams.set("t", timestamp);
res.json({ ogImageUrl: url.toString() });
});
export default router;React Component
// src/components/OgImage.jsx
import { useState, useEffect } from "react";
export function useOgImage(params) {
const [ogImageUrl, setOgImageUrl] = useState(null);
useEffect(() => {
const searchParams = new URLSearchParams(params);
fetch(`/api/og?${searchParams}`)
.then((res) => res.json())
.then((data) => setOgImageUrl(data.ogImageUrl))
.catch((err) => console.error("Failed to generate OG image:", err));
}, [JSON.stringify(params)]);
return ogImageUrl;
}Blog Post Example
// src/pages/BlogPost.jsx
import { useEffect, useState } from "react";
import { useOgImage } from "../components/OgImage";
export default function BlogPost({ post }) {
const ogImageUrl = useOgImage({
title: post.title,
template: "blog",
subtitle: post.excerpt,
});
useEffect(() => {
const ogImage = document.querySelector('meta[property="og:image"]');
if (ogImage && ogImageUrl) {
ogImage.setAttribute("content", ogImageUrl);
}
}, [ogImageUrl]);
return (
<article>
<h1>{post.title}</h1>
<p>{post.excerpt}</p>
</article>
);
}React Helmet (SSR)
// src/pages/BlogPost.jsx
import { Helmet } from "react-helmet-async";
import { useOgImage } from "../components/OgImage";
export default function BlogPost({ post }) {
const ogImageUrl = useOgImage({
title: post.title,
template: "blog",
subtitle: post.excerpt,
});
return (
<>
<Helmet>
<meta
property="og:image"
content={ogImageUrl || "/fallback-og.png"}
/>
<meta property="og:title" content={post.title} />
<meta property="og:description" content={post.excerpt} />
</Helmet>
<article>
<h1>{post.title}</h1>
</article>
</>
);
}Next.js (React Framework)
For Next.js, see the dedicated Next.js App Router and Next.js Pages Router tutorials.
Remix
// app/routes/blog.$slug.jsx
import { json } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
import { generateSignedOgUrl } from "~/lib/og-image-signer.server";
export async function loader({ params }) {
const post = await getPost(params.slug);
const ogImageUrl = generateSignedOgUrl({
template: "blog",
title: post.title,
subtitle: post.excerpt,
});
return json({ post, ogImageUrl });
}
export function meta({ data }) {
return [
{ title: data.post.title },
{ property: "og:image", content: data.ogImageUrl },
{ property: "og:title", content: data.post.title },
];
}
export default function BlogPost() {
const { post } = useLoaderData();
return (
<article>
<h1>{post.title}</h1>
</article>
);
}Server-side signer (Node.js)
// lib/og-image-signer.server.js
import crypto from "crypto";
export function generateSignedOgUrl(params, options = {}) {
const {
baseUrl = "https://fastog.com/api/v1/og",
apiKey = process.env.FASTOG_API_KEY,
hmacSecret = process.env.FASTOG_HMAC_SECRET,
} = options;
const timestamp = Math.floor(Date.now() / 1000);
const url = new URL(baseUrl);
for (const [key, value] of Object.entries(params)) {
url.searchParams.set(key, value);
}
const apiKey = apiKey;
const message = `GET${url.pathname}${timestamp}${apiKey}`;
const signature = crypto
.createHmac("sha256", hmacSecret)
.update(message)
.digest("hex");
url.searchParams.set("key", apiKey);
url.searchParams.set("s", signature);
url.searchParams.set("t", timestamp);
return url.toString();
}Environment Variables
# .env (Vite)
VITE_FASTOG_API_KEY=sk_your_full_api_key_here
VITE_FASTOG_HMAC_SECRET=your_64_char_hex_secret_here
# .env (Create React App)
REACT_APP_FASTOG_API_KEY=sk_your_full_api_key_here
REACT_APP_FASTOG_HMAC_SECRET=your_64_char_hex_secret_here
# Server .env
FASTOG_API_KEY=sk_your_full_api_key_here
FASTOG_HMAC_SECRET=your_64_char_hex_secret_hereCommon Pitfalls
Exposing HMAC Secret in Client Code
❌ Wrong:
// In browser code
const ogUrl = generateSignedOgUrl(params, {
hmacSecret: import.meta.env.VITE_FASTOG_HMAC_SECRET,
});✅ Correct:
// Call your backend API
const res = await fetch(`/api/og?title=${post.title}`);
const { ogImageUrl } = await res.json();Not Updating Meta Tags
Social crawlers need meta tags in the initial HTML. Use SSR (Next.js, Remix) or a backend proxy for proper OG image support.
Full API Reference
Related
Try it free — no signup required
Preview and test dynamic OG images in seconds.
Open the Free OG Image Tester