Build a Meta Tag Generator 🏷️
If you want your website to look good when shared on Twitter or Facebook, you need Meta Tags. Hand-writing them is error-prone. In this guide, we will build a tool that generates the "Holy Trinity" of SEO tags:
- Basic HTML: For Google Search.
- Open Graph (OG): For Facebook, LinkedIn, and Discord.
- Twitter Cards: For X (Twitter).
Step 1: The Template Logic 📝
The core of this tool is a simple Template String function. We take user inputs (title, description, image) and inject them into standard HTML boilerplate.
const generateTags = (data) => {
return `<!-- Basic SEO -->
<title>${data.title}</title>
<meta name="description" content="${data.description}" />
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website" />
<meta property="og:title" content="${data.title}" />
<meta property="og:description" content="${data.description}" />
<meta property="og:image" content="${data.image}" />
<!-- Twitter -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="${data.title}" />
<meta name="twitter:description" content="${data.description}" />
<meta name="twitter:image" content="${data.image}" />`;
}
Step 2: Character Limits (SEO Best Practices) 📏
Google cuts off titles after 60 characters. Descriptions get truncated after 160 characters. A good tool guides the user.
<div className="input-group">
<label>Description</label>
<textarea
value={desc}
onChange={e => setDesc(e.target.value)}
maxLength={160}
/>
<span className={desc.length > 150 ? "text-red-500" : "text-gray-500"}>
{desc.length}/160
</span>
</div>
Step 3: The Full React Component 💻
Here is a clean implementation with live preview and copying.
"use client"
import { useState } from "react"
import { Copy, Check, Search } from "lucide-react"
export default function MetaGenerator() {
const [title, setTitle] = useState("")
const [desc, setDesc] = useState("")
const [image, setImage] = useState("")
const [copied, setCopied] = useState(false)
const code = `
<!-- Primary Meta Tags -->
<title>${title || "Title"}</title>
<meta name="title" content="${title || "Title"}" />
<meta name="description" content="${desc || "Description"}" />
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website" />
<meta property="og:url" content="https://example.com/" />
<meta property="og:title" content="${title || "Title"}" />
<meta property="og:description" content="${desc || "Description"}" />
<meta property="og:image" content="${image || "https://example.com/image.png"}" />
<!-- Twitter -->
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:url" content="https://example.com/" />
<meta property="twitter:title" content="${title || "Title"}" />
<meta property="twitter:description" content="${desc || "Description"}" />
<meta property="twitter:image" content="${image || "https://example.com/image.png"}" />
`.trim()
const handleCopy = () => {
navigator.clipboard.writeText(code)
setCopied(true)
setTimeout(() => setCopied(false), 2000)
}
return (
<div className="grid md:grid-cols-2 gap-8 max-w-4xl mx-auto p-4">
{/* INPUT FORM */}
<div className="space-y-6 bg-white p-6 rounded-xl shadow-sm border">
<h2 className="text-xl font-bold flex items-center gap-2">
<Search className="w-5 h-5" /> SEO Details
</h2>
<div>
<label className="text-sm font-bold text-slate-700">Page Title</label>
<input
value={title} onChange={e => setTitle(e.target.value)}
className="w-full p-2 border rounded mt-1"
placeholder="My Awesome Website"
maxLength={60}
/>
<div className="text-right text-xs text-slate-400 mt-1">{title.length}/60</div>
</div>
<div>
<label className="text-sm font-bold text-slate-700">Meta Description</label>
<textarea
value={desc} onChange={e => setDesc(e.target.value)}
className="w-full p-2 border rounded mt-1 h-24"
placeholder="Learn to code with our free tutorials..."
maxLength={160}
/>
<div className="text-right text-xs text-slate-400 mt-1">{desc.length}/160</div>
</div>
<div>
<label className="text-sm font-bold text-slate-700">Image URL (OG Image)</label>
<input
value={image} onChange={e => setImage(e.target.value)}
className="w-full p-2 border rounded mt-1"
placeholder="https://example.com/og.png"
/>
</div>
</div>
{/* CODE OUTPUT */}
<div className="space-y-4">
<div className="flex items-center justify-between">
<h2 className="font-bold text-slate-700">HTML Output</h2>
<button
onClick={handleCopy}
className="flex items-center gap-2 text-xs font-bold bg-slate-900 text-white px-3 py-1.5 rounded-full hover:bg-black transition"
>
{copied ? <Check size={14}/> : <Copy size={14}/>}
{copied ? "Copied" : "Copy Code"}
</button>
</div>
<div className="bg-slate-900 text-slate-300 p-4 rounded-xl font-mono text-xs overflow-x-auto shadow-2xl">
<pre>{code}</pre>
</div>
{/* GOOGLE PREVIEW */}
<div className="bg-white p-4 rounded-lg shadow-sm border">
<div className="text-xs text-slate-500 mb-2">Google Preview</div>
<div className="text-lg text-blue-800 hover:underline cursor-pointer truncate">
{title || "Page Title"}
</div>
<div className="text-xs text-green-700 truncate">
https://example.com/article
</div>
<div className="text-sm text-slate-600 line-clamp-2 mt-1">
{desc || "This is how your page will appear in Google search results. Make sure to include keywords."}
</div>
</div>
</div>
</div>
)
}
Step 4: Adding a Google Preview 👀
To make your tool "Premium", add a live preview box. It's just HTML/CSS mimicking Google's search result style (Blue Title, Green URL, Gray Text). This gives users instant feedback on readability and truncation.