OG-Bildgenerierung für React-Apps
Generieren Sie og:image mit React über die FastOG-API – mit einem Dynamic-API-Abonnement (erstellen Sie Ihren API-Schlüssel im Dashboard), abonnementbasiert, HMAC-gesichert. Diese Anleitung zeigt Ihnen, wie Sie ein Signatur-Dienstprogramm (Signer) für HMAC-signierte OG-Bild-URLs erstellen und dynamische Open-Graph-Bilder im Handumdrehen rendern. Testen Sie Ihren Entwurf vor der Veröffentlichung mit dem kostenlosen OG-Bild-Tester (Free OG Image Tester).
Schnellstart
1. Signer-Dienstprogramm erstellen
// src/utils/og-image-signer.js
export async 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");
}
// RFC-3986 percent-encoding: keep A-Za-z0-9-._~
const enc = (s) =>
encodeURIComponent(s).replace(
/[!'()*]/g,
(c) => "%" + c.charCodeAt(0).toString(16).toUpperCase(),
);
// Every query param except the signature, INCLUDING key
const all = { ...params, key: apiKey };
// Canonical query: sorted enc(key)=enc(value) pairs joined with &
const pairs = [];
for (const [k, v] of Object.entries(all)) {
for (const vv of Array.isArray(v) ? v : [v]) {
pairs.push(`${enc(k)}=${enc(vv)}`);
}
}
pairs.sort();
const canonical = pairs.join("&");
// Sign the exact bytes the server will verify (Web Crypto HMAC-SHA256)
const signingKey = await crypto.subtle.importKey(
"raw",
new TextEncoder().encode(hmacSecret),
{ name: "HMAC", hash: "SHA-256" },
false,
["sign"],
);
const signature = Array.from(
new Uint8Array(
await crypto.subtle.sign(
"HMAC",
signingKey,
new TextEncoder().encode(`GET/api/v1/og\n${canonical}`),
),
),
)
.map((b) => b.toString(16).padStart(2, "0"))
.join("");
// Build the URL from the same pairs so bytes match the canonical form
const url = new URL(baseUrl);
for (const [k, v] of Object.entries(all)) {
for (const vv of Array.isArray(v) ? v : [v]) {
url.searchParams.append(Array.isArray(v) ? `${k}[]` : k, vv);
}
}
url.searchParams.set("s", signature);
return url.toString();
}
⚠️ Wichtig: In React-SPAs erfolgt das Signieren clientseitig. Für die Produktion sollten Sie das Signieren in eine Backend-API-Route verlagern, um das HMAC-Geheimnis sicher aufzubewahren.
Backend-API-Route (Empfohlen)
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 apiKey = process.env.FASTOG_API_KEY;
const hmacSecret = process.env.FASTOG_HMAC_SECRET;
const enc = (s) =>
encodeURIComponent(s).replace(
/[!'()*]/g,
(c) => "%" + c.charCodeAt(0).toString(16).toUpperCase(),
);
const all = { template, title, ...params, key: apiKey };
const pairs = [];
for (const [k, v] of Object.entries(all)) {
for (const vv of Array.isArray(v) ? v : [v]) {
pairs.push(`${enc(k)}=${enc(vv)}`);
}
}
pairs.sort();
const canonical = pairs.join("&");
const signature = crypto
.createHmac("sha256", hmacSecret)
.update(`GET/api/v1/og\n${canonical}`)
.digest("hex");
const url = new URL("https://fastog.com/api/v1/og");
for (const [k, v] of Object.entries(all)) {
for (const vv of Array.isArray(v) ? v : [v]) {
url.searchParams.append(Array.isArray(v) ? `${k}[]` : k, vv);
}
}
url.searchParams.set("s", signature);
res.json({ ogImageUrl: url.toString() });
});
export default router;
React-Komponente
// 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;
}
Blogbeitrag-Beispiel
// 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)
Für Next.js finden Sie die speziellen Tutorials Next.js App Router und Next.js Pages Router.
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>
);
}
Serverseitiger 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;
// RFC-3986 percent-encoding: keep A-Za-z0-9-._~
const enc = (s) =>
encodeURIComponent(s).replace(
/[!'()*]/g,
(c) => "%" + c.charCodeAt(0).toString(16).toUpperCase(),
);
// Every query param except the signature, INCLUDING key
const all = { ...params, key: apiKey };
// Canonical query: sorted enc(key)=enc(value) pairs joined with &
const pairs = [];
for (const [k, v] of Object.entries(all)) {
for (const vv of Array.isArray(v) ? v : [v]) {
pairs.push(`${enc(k)}=${enc(vv)}`);
}
}
pairs.sort();
const canonical = pairs.join("&");
// Sign the exact bytes the server will verify
const signature = crypto
.createHmac("sha256", hmacSecret)
.update(`GET/api/v1/og\n${canonical}`)
.digest("hex");
// Build the URL from the same pairs so bytes match the canonical form
const url = new URL(baseUrl);
for (const [k, v] of Object.entries(all)) {
for (const vv of Array.isArray(v) ? v : [v]) {
url.searchParams.append(Array.isArray(v) ? `${k}[]` : k, vv);
}
}
url.searchParams.set("s", signature);
return url.toString();
}
Umgebungsvariablen
# .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_here
Häufige Fehlerquellen
HMAC-Geheimnis im Client-Code offenlegen
❌ Falsch:
// In browser code
const ogUrl = generateSignedOgUrl(params, {
hmacSecret: import.meta.env.VITE_FASTOG_HMAC_SECRET,
});
✅ Richtig:
// Call your backend API
const res = await fetch(`/api/og?title=${post.title}`);
const { ogImageUrl } = await res.json();
Meta-Tags nicht aktualisieren
Social-Crawler benötigen Meta-Tags im initialen HTML. Verwenden Sie SSR (Next.js, Remix) oder einen Backend-Proxy für eine ordnungsgemäße OG-Bild-Unterstützung.