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 webpages
  • article: Blog posts, news articles
  • product: Product pages
  • profile: 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
Facebook 1200×630 600×315
Twitter 1200×600 440×220
WeChat 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#

  1. summary: Small image (120×120), good for content summaries
  2. summary_large_image: Large image (280×150), recommended
  3. app: App download cards
  4. 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, '&amp;')   // Must be processed first
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')  // Attributes use double quotes
}

Key point: & must be processed first, otherwise you’ll double-escape. For example, &lt; escaped for < first, then & would become &amp;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:

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