← Browser Run / browser-run / how-to
Generování OG obrázků pro weby Astro
Obrázky Open Graph (OG) jsou náhledové obrázky, které se zobrazí při sdílení odkazu na sociálních sítích. Místo ručního vytváření těchto obrázků pro každý příspěvek na blogu můžete pomocí Cloudflare Browser Run automaticky generovat značkové náhledové obrázky pro sociální sítě ze šablony Astro.
V tomto tutoriálu:
- Vytvořte stránku v Astro, která vykresluje návrh vašeho OG image.
- Pomocí Browser Run pořiďte snímek dané stránky ve formátu PNG.
- Poskytujte vygenerované obrázky prohledávačům sociálních sítí.
Předpoklady
- Účet Cloudflare s Browser Run povoleno
- Web Astro nasazený na Cloudflare Workers
- Základní znalost Astro a Cloudflare Workers
1. Vytvoření šablony OG obrázku
Vytvořte trasu v Astro, která vykresluje návrh vašeho OG image. Tato stránka je jediným zdrojem pravdy pro rozvržení vašeho obrázku.
Vytvořit src/pages/social-card.astro:
---
export const prerender = false;
const title = Astro.url.searchParams.get("title") || "Untitled";
const image = Astro.url.searchParams.get("image");
const author = Astro.url.searchParams.get("author");
---
<html>
<head>
<meta charset="utf-8" />
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
width: 1200px;
height: 630px;
display: flex;
flex-direction: column;
justify-content: flex-end;
padding: 60px;
font-family: system-ui, sans-serif;
background: linear-gradient(135deg, #f38020 0%, #f9a825 100%);
color: white;
}
.title {
font-size: 64px;
font-weight: bold;
line-height: 1.1;
margin-bottom: 24px;
}
.author {
font-size: 24px;
opacity: 0.9;
}
.logo {
position: absolute;
top: 60px;
left: 60px;
height: 40px;
}
</style>
</head>
<body>
<img class="logo" src="/your-logo.png" alt="Your logo" />
<h1 class="title">{title}</h1>
{author && <p class="author">By {author}</p>}
</body>
</html>Spusťte vývojový server Astro a otestujte šablonu:
npm run devOtestujte lokálně tak, že navštívíte http://localhost:4321/social-card?title=My%20Blog%20Post&author=Omar.
Než budete pokračovat, nasaďte svůj web, abyste zajistili /social-card trasa je aktivní:
# For Cloudflare Workers
npx wrangler deployAktualizujte BASE_URL ve skriptu níže tak, aby odpovídal adrese URL vašeho nasazeného webu.
2. Generování OG obrázků při sestavení
Vygenerujte všechny OG obrázky během procesu sestavení Astro pomocí Cloudflare Browser Run Quick Actions.
Vytvořit scripts/generate-social-cards.ts:
import {
existsSync,
mkdirSync,
readdirSync,
readFileSync,
writeFileSync,
} from "fs";
import { join } from "path";
// Configuration
const BASE_URL = "https://your-site.com"; // Your deployed site URL
const CF_API = "https://api.cloudflare.com/client/v4/accounts";
const OUTPUT_DIR = "public/social-cards"; // Output directory for generated images
const POSTS_DIR = "src/data/posts"; // Directory containing your markdown posts (adjust to match your project)
interface Post {
slug: string;
title: string;
author?: string;
}
/** Extract a frontmatter field value from raw markdown content. */
function getFrontmatterField(content: string, field: string): string | null {
const match = content.match(new RegExp(`^${field}:\\s*"?([^"\\n]+)"?`, "m"));
return match ? match[1].trim() : null;
}
/**
* Read all post files and return { slug, title, author }[].
* This function scans the POSTS_DIR for markdown files, extracts frontmatter
* fields (slug, title, author), and returns an array of post objects.
* Falls back to filename for slug and slug for title if frontmatter is missing.
*/
function readPosts(): Post[] {
if (!existsSync(POSTS_DIR)) return [];
const files = readdirSync(POSTS_DIR).filter((f) => f.endsWith(".md"));
return files.map((file) => {
const raw = readFileSync(join(POSTS_DIR, file), "utf-8");
const slug = getFrontmatterField(raw, "slug") ?? file.replace(/\.md$/, "");
const title = getFrontmatterField(raw, "title") ?? slug;
const author = getFrontmatterField(raw, "author") ?? undefined;
return { slug, title, author };
});
}
/**
* Capture a screenshot using Cloudflare Browser Run Quick Actions
*/
async function captureScreenshot(
accountId: string,
apiToken: string,
pageUrl: string,
): Promise<ArrayBuffer> {
const endpoint = `${CF_API}/${accountId}/browser-rendering/screenshot`;
const res = await fetch(endpoint, {
method: "POST",
headers: {
Authorization: `Bearer ${apiToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
url: pageUrl,
viewport: { width: 1200, height: 630 }, // Standard OG image size
gotoOptions: { waitUntil: "networkidle0" }, // Wait for page to fully load
}),
});
if (!res.ok) {
const text = await res.text();
throw new Error(`Screenshot API returned ${res.status}: ${text}`);
}
return res.arrayBuffer();
}
async function main() {
// Read credentials from environment variables
const accountId = process.env.CF_ACCOUNT_ID;
const apiToken = process.env.CF_API_TOKEN;
if (!accountId || !apiToken) {
console.error("Error: CF_ACCOUNT_ID and CF_API_TOKEN required");
process.exit(1);
}
// Check if --force flag is passed to regenerate all images
const force = process.argv.includes("--force");
// Read posts from markdown files
const posts = readPosts();
if (posts.length === 0) {
console.log("No posts found. Check your POSTS_DIR path.");
process.exit(0);
}
console.log(`Found ${posts.length} posts to process\n`);
// Ensure output directory exists
mkdirSync(OUTPUT_DIR, { recursive: true });
let generated = 0;
let skipped = 0;
// Generate social card for each post
for (let i = 0; i < posts.length; i++) {
const post = posts[i];
const outPath = join(OUTPUT_DIR, `${post.slug}.png`);
const label = `[${i + 1}/${posts.length}]`;
// Skip if file exists and --force flag not set
if (!force && existsSync(outPath)) {
console.log(`${label} ${post.slug}.png — skipped (exists)`);
skipped++;
continue;
}
// Build URL with query parameters for the OG template
const params = new URLSearchParams({
title: post.title,
author: post.author || "",
});
const url = `${BASE_URL}/social-card?${params}`;
try {
// Capture screenshot and save to file
const png = await captureScreenshot(accountId, apiToken, url);
writeFileSync(outPath, Buffer.from(png));
console.log(`${label} ${post.slug}.png — done`);
generated++;
} catch (err) {
console.error(`${label} ${post.slug}.png — failed:`, err);
}
// Rate limiting: small delay between requests
if (i < posts.length - 1) {
await new Promise((resolve) => setTimeout(resolve, 200));
}
}
console.log(`\nDone. Generated: ${generated}, Skipped: ${skipped}`);
}
main();Přihlašovací údaje Cloudflare nastavte jako proměnné prostředí:
export CF_ACCOUNT_ID=your_account_id
export CF_API_TOKEN=your_api_tokenSpusťte skript pro generování obrázků:
# Generate new images only
bun scripts/generate-social-cards.ts
# Regenerate all images
bun scripts/generate-social-cards.ts --forceVolitelně přidejte do svého build skriptu v package.json:
{
"scripts": {
"build": "bun scripts/generate-social-cards.ts && astro build"
}
}3. Přidání metaznaček OG na stránky
Upravte šablonu příspěvku na blogu tak, aby odkazovala na vygenerované obrázky:
---
// src/layouts/BlogPost.astro
const { title, slug, author } = Astro.props;
const ogImageUrl = `/social-cards/${slug}.png`;
---
<html>
<head>
<meta property="og:title" content={title} />
<meta property="og:image" content={ogImageUrl} />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:image" content={ogImageUrl} />
</head>
<body>
<slot />
</body>
</html>4. Testování OG obrázků
Před testováním nezapomeňte nasadit web s nově vygenerovanými obrázky sociálních karet:
# For Cloudflare Workers
npx wrangler deployPomocí těchto nástrojů ověřte, že se vaše OG obrázky vykreslují správně:
Přizpůsobte šablonu
Přidání obrázku na pozadí
---
const title = Astro.url.searchParams.get("title") || "Untitled";
const image = Astro.url.searchParams.get("image");
---
<body style={image ? `background-image: url(${image})` : undefined}>
<!-- content -->
</body>Použití vlastních písem
<head>
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@700&display=swap"
rel="stylesheet"
/>
<style>
body {
font-family: "Inter", sans-serif;
}
</style>
</head>Přidání Tailwind CSS
Pokud váš web Astro používá Tailwind, můžete ho použít ve své šabloně OG:
---
import "../styles/global.css";
---
<body
class="flex h-[630px] w-[1200px] flex-col justify-end bg-gradient-to-br from-orange-500 to-amber-500 p-16 text-white"
>
<h1 class="mb-6 text-6xl leading-tight font-bold">{title}</h1>
</body>Úvahy o výkonu
Optimalizace obrázků
Zvažte zpracování vygenerovaných obrázků prostřednictvím Cloudflare Images nebo Image Resizing pro další optimalizaci:
const optimizedUrl = `https://your-domain.com/cdn-cgi/image/width=1200,format=auto/social-cards/${slug}.png`;Další kroky
Váš web Astro nyní automaticky generuje obrázky OG pomocí Browser Run. Když sdílíte odkaz na sociálních sítích, crawlery si vygenerovaný obrázek stáhnou ze statické cesty.
Odtud můžete:
- Přizpůsobte si šablonu pomocí vlastní písma, Tailwind CSS, nebo obrázky na pozadí.
- Přidejte logiku pro invalidaci cache, aby se obrázky znovu vygenerovaly při změně obsahu příspěvku.
- Použijte Cloudflare Images nebo Změna velikosti obrázků pro další optimalizaci.