Building a Meta Tag Generator: Open Graph, Twitter Card, and SEO Optimization
Building a Meta Tag Generator: Open Graph, Twitter Card, and SEO Optimization#
When I added sharing functionality to a project recently, I noticed that the preview images on WeChat and Twitter were always wrong. After digging deeper, I realized these platforms don’t just scrape any content - they follow specific protocols: Open Graph and Twitter Card. I decided to build a meta tag generator to truly understand this system.
Open Graph Protocol: The Standard for Social Media Previews#
Open Graph was proposed by Facebook in 2010 and is now supported by all major social platforms. At its core are <meta> tags identified by property="og:xxx":
<meta property="og:title" content="Building a Meta Tag Generator">
<meta property="og:description" content="Deep dive into Open Graph and Twitter Card protocols">
<meta property="og:image" content="https://jsokit.com/og-image.png">
<meta property="og:url" content="https://jsokit.com/blog/meta-tag">
<meta property="og:type" content="article">
When users share a link, the platform fetches these tags and generates a preview card. Without these tags? The platform has to guess, often with poor results.
Choosing og:type#
og:type determines how the card is displayed. Common values:
website: Default, for regular webpagesarticle: Blog posts, news articlesproduct: Product pagesprofile: User profiles
Different types support different additional properties. For example, article can include article:published_time and article:author.
The og:image Size Trap#
This is where most mistakes happen. Recommended sizes for different platforms:
| Platform | Recommended | Minimum |
|---|---|---|
| 1200×630 | 600×315 | |
| 1200×600 | 440×220 | |
| 300×300 | - |
In practice, I chose 1200×630 as the default - Facebook’s recommended 1.91:1 ratio that Twitter also accepts:
const [form, setForm] = useState({
imageWidth: 1200,
imageHeight: 630,
// ...
})
Twitter Card: A Separate Meta Tag System#
Twitter doesn’t fully follow Open Graph. Instead, it has its own Twitter Card specification. The most commonly used is summary_large_image:
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="@username">
<meta name="twitter:title" content="Building a Meta Tag Generator">
<meta name="twitter:description" content="Deep dive into Open Graph and Twitter Card">
<meta name="twitter:image" content="https://jsokit.com/og-image.png">
Notice the attribute name changes from property to name - a key difference between the two protocols.
Four Types of Twitter Cards#
- summary: Small image (120×120), good for content summaries
- summary_large_image: Large image (280×150), recommended
- app: App download cards
- player: Video/audio players
In the implementation, I default to summary_large_image for best visual impact:
tags.push('<meta name="twitter:card" content="summary_large_image">')
HTML Escaping: Preventing XSS and Format Issues#
Meta tag values are inserted into HTML attributes and must be properly escaped. Otherwise:
<!-- Dangerous! User input might contain malicious code -->
<meta name="description" content="<script>alert('xss')</script>">
The escape function:
function escapeHtml(s: string): string {
return s
.replace(/&/g, '&') // Must be processed first
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"') // Attributes use double quotes
}
Key point: & must be processed first, otherwise you’ll double-escape. For example, < escaped for < first, then & would become &lt;.
Real-time Preview: Simulating Social Platform Rendering#
The core experience of the tool is real-time preview. Users see how Facebook/Twitter will display as they type:
function Preview({ form }) {
return (
<div className="preview-card">
{/* Simulate Facebook's preview card */}
<div className="preview-image" style={{ aspectRatio: '1200/630' }}>
<img src={form.image} />
</div>
<div className="preview-content">
<p className="preview-url">{form.url}</p>
<p className="preview-title">{form.title}</p>
<p className="preview-description">{form.description}</p>
</div>
</div>
)
}
Using aspectRatio: '1200/630' maintains image proportions - even before the actual image loads, the placeholder has the correct shape.
Handling Image Load Failures#
Users might enter incorrect image URLs. We need graceful degradation:
<img
src={form.image}
onError={(e) => {
(e.target as HTMLImageElement).style.display = 'none'
}}
/>
When image loading fails, hide it instead of showing a broken image icon.
Tag Generation Implementation#
Complete tag generation logic:
function generateTags(form: FormData): string {
const tags: string[] = []
// Basic HTML tags
tags.push('<meta charset="UTF-8">')
tags.push('<meta name="viewport" content="width=device-width, initial-scale=1.0">')
// SEO tags
if (form.title) {
tags.push(`<title>${escapeHtml(form.title)}</title>`)
tags.push(`<meta name="description" content="${escapeHtml(form.description)}">`)
}
// Open Graph tags
if (form.title) tags.push(`<meta property="og:title" content="${escapeHtml(form.title)}">`)
if (form.description) tags.push(`<meta property="og:description" content="${escapeHtml(form.description)}">`)
if (form.url) tags.push(`<meta property="og:url" content="${escapeHtml(form.url)}">`)
if (form.image) {
tags.push(`<meta property="og:image" content="${escapeHtml(form.image)}">`)
tags.push(`<meta property="og:image:width" content="${form.imageWidth}">`)
tags.push(`<meta property="og:image:height" content="${form.imageHeight}">`)
}
// Twitter Card tags
tags.push('<meta name="twitter:card" content="summary_large_image">')
if (form.twitterHandle) {
tags.push(`<meta name="twitter:site" content="${escapeHtml(form.twitterHandle)}">`)
}
return tags.join('\n')
}
Why Conditionals?#
Not all fields are required. If a user doesn’t provide an image, we shouldn’t generate og:image tags. Empty tags can cause parsing errors on platforms - better to omit them entirely.
og:locale for Multi-language Support#
For multi-language websites, og:locale specifies the page language:
const localeOptions = [
'en_US', 'zh_CN', 'zh_TW', 'ja_JP', 'ko_KR',
'fr_FR', 'de_DE', 'es_ES', 'pt_BR', 'ru_RU',
// ... 22 language options total
]
The format is languageCode_countryCode following POSIX standards. This helps platforms handle content language correctly - for instance, WeChat uses locale to determine whether to show Chinese previews.
Validation Tools#
After generating tags, how do you verify they’re correct? Each platform provides debugging tools:
- Facebook: https://developers.facebook.com/tools/debug/
- Twitter: https://cards-dev.twitter.com/validator
- WeChat: Test by sharing directly (no public tool available)
I recommend validating with these tools before publishing - they catch many issues like wrong image sizes, unescaped URLs, or missing required fields.
The Result#
Based on these principles, I built an online tool: Meta Tag Generator
Key features:
- Open Graph + Twitter Card dual protocol support
- Real-time preview (simulating Facebook cards)
- 22 language/locale options
- HTML escaping to prevent XSS
- One-click copy of generated code
The implementation isn’t difficult, but there are many details to get right. Hope this article helps you avoid those pitfalls.
Related Tools: JSON-LD Generator | Sitemap Generator