This document outlines the implementation of a dynamic Open Graph (OG) image generation API for the Viking Rune Converter application.
To provide dynamically generated social media preview images when users share their rune conversion results.
-
Create a Netlify Function that generates a simple image with:
- The user's name
- The converted rune text
- Viking theme styling
-
The function will receive query parameters:
name: The user's name (Korean or English)rune: The converted rune text
-
Use a simple HTML/CSS template to render the image
-
Return the image with appropriate headers for caching and content type
Create a new file at netlify/functions/og.js:
// netlify/functions/og.js
const chromium = require('@sparticuz/chromium');
const puppeteer = require('puppeteer-core');
exports.handler = async function(event, context) {
const { name = '', rune = '' } = event.queryStringParameters;
// Simple HTML template for the OG image
const html = `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body {
margin: 0;
padding: 0;
width: 1200px;
height: 630px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: #FAF0E6;
font-family: system-ui, sans-serif;
color: #8B4513;
position: relative;
overflow: hidden;
}
.border {
position: absolute;
top: 20px;
left: 20px;
right: 20px;
bottom: 20px;
border: 8px solid #8B4513;
}
.inner-border {
position: absolute;
top: 35px;
left: 35px;
right: 35px;
bottom: 35px;
border: 2px solid #CD853F;
}
.title {
font-size: 48px;
font-weight: bold;
margin-bottom: 30px;
text-align: center;
}
.name {
font-size: 36px;
margin-bottom: 40px;
text-align: center;
}
.rune {
font-size: 120px;
font-weight: bold;
margin-bottom: 50px;
text-align: center;
letter-spacing: 10px;
}
.subtitle {
font-size: 24px;
text-align: center;
margin-bottom: 20px;
}
.footer {
position: absolute;
bottom: 60px;
font-size: 18px;
opacity: 0.7;
}
</style>
</head>
<body>
<div class="border"></div>
<div class="inner-border"></div>
<div class="title">바이킹 룬 문자 변환기</div>
<div class="name">${name}</div>
<div class="rune">${rune}</div>
<div class="subtitle">고대 바이킹 엘더 푸타르크 룬 문자</div>
<div class="footer">rune.vikingconverter.com</div>
</body>
</html>
`;
try {
// Setup browser
const browser = await puppeteer.launch({
args: chromium.args,
defaultViewport: {
width: 1200,
height: 630
},
executablePath: await chromium.executablePath(),
headless: true,
});
// Create a new page
const page = await browser.newPage();
// Set content and wait until network is idle
await page.setContent(html, { waitUntil: 'networkidle0' });
// Take a screenshot
const screenshot = await page.screenshot({
type: 'png',
encoding: 'base64'
});
// Close browser
await browser.close();
// Return the image with appropriate headers
return {
statusCode: 200,
headers: {
'Content-Type': 'image/png',
'Cache-Control': 'public, max-age=31536000, s-maxage=31536000'
},
body: screenshot,
isBase64Encoded: true
};
} catch (error) {
console.error('Error generating OG image:', error);
// Return a fallback image or error
return {
statusCode: 500,
body: JSON.stringify({ error: 'Failed to generate image' }),
headers: {
'Content-Type': 'application/json'
}
};
}
};- This implementation requires adding
@sparticuz/chromiumandpuppeteer-coreto the project dependencies - For production, consider optimizing the image generation or using a dedicated image generation service
- The function uses Puppeteer to generate the image, which requires additional setup in Netlify
- A simpler implementation could return HTML/CSS instead of an image for services that support it
Add these dependencies:
npm install @sparticuz/chromium puppeteer-core
Update the client/index.html to include a dynamic meta tag:
<!-- In the head section -->
<meta property="og:image" content="%PUBLIC_URL%/og-image.jpg" data-dynamic-og-image="true" />Then in the ShareModal.tsx, update the sharing functionality to set the proper OG image URL before sharing.