Skip to content

Instantly share code, notes, and snippets.

@arodu
Created July 6, 2026 19:27
Show Gist options
  • Select an option

  • Save arodu/5a3b2e792295ec387ce8f16bbe5db9a0 to your computer and use it in GitHub Desktop.

Select an option

Save arodu/5a3b2e792295ec387ce8f16bbe5db9a0 to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Local Meta Tag Previewer</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
.preview-card { max-width: 550px; }
.discord-card { border-left: 4px solid #5865F2; }
</style>
</head>
<body class="bg-slate-900 text-slate-100 min-h-screen p-6">
<div class="max-w-6xl mx-auto">
<header class="mb-8 text-center">
<h1 class="text-3xl font-bold text-indigo-400">Meta Tag & Card Previewer</h1>
<p class="text-slate-400 text-sm mt-2">Test Open Graph and Twitter Card meta tags locally.</p>
</header>
<div class="bg-slate-800 p-4 rounded-lg shadow-lg flex gap-4 mb-8">
<input type="url" id="urlInput" placeholder="https://example.com (empty = current site)"
class="flex-1 bg-slate-700 border border-slate-600 rounded px-4 py-2 text-white focus:outline-none focus:border-indigo-500">
<button onclick="analyzeMetaTags()" class="bg-indigo-600 hover:bg-indigo-500 text-white font-semibold px-6 py-2 rounded transition whitespace-nowrap">
Analyze
</button>
</div>
<div id="loading" class="hidden text-center text-indigo-400 my-4 font-semibold animate-pulse">Loading and processing site...</div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
<div class="space-y-8">
<!-- Open Graph -->
<div class="bg-slate-800 p-6 rounded-lg shadow-md">
<h2 class="text-xl font-bold mb-4 text-blue-400 border-b border-slate-700 pb-2">Open Graph (Facebook / LinkedIn)</h2>
<div class="preview-card bg-white text-black rounded-md overflow-hidden border border-gray-200 shadow-sm mx-auto">
<img id="ogImage" src="" class="w-full h-64 object-cover hidden">
<div class="p-3 bg-gray-100 border-t border-gray-200">
<div id="ogDomain" class="text-xs text-gray-500 uppercase tracking-wide font-semibold"></div>
<div id="ogTitle" class="font-bold text-gray-900 text-lg mt-1 truncate"></div>
<div id="ogDesc" class="text-sm text-gray-600 mt-1 line-clamp-2"></div>
</div>
</div>
</div>
<!-- Twitter Large Image -->
<div class="bg-slate-800 p-6 rounded-lg shadow-md">
<h2 class="text-xl font-bold mb-4 text-sky-400 border-b border-slate-700 pb-2">Twitter / X (Summary Large Image)</h2>
<div class="preview-card bg-black text-white rounded-xl overflow-hidden border border-zinc-800 mx-auto">
<img id="twImage" src="" class="w-full h-60 object-cover hidden">
<div class="p-3 border-t border-zinc-800 bg-zinc-950">
<div id="twDomain" class="text-xs text-zinc-500"></div>
<div id="twTitle" class="font-bold text-sm mt-0.5 truncate"></div>
<div id="twDesc" class="text-xs text-zinc-400 mt-0.5 line-clamp-2"></div>
</div>
</div>
</div>
<!-- Twitter Summary (small image) -->
<div class="bg-slate-800 p-6 rounded-lg shadow-md">
<h2 class="text-xl font-bold mb-4 text-sky-400 border-b border-slate-700 pb-2">Twitter / X (Summary)</h2>
<div class="preview-card bg-black text-white rounded-xl overflow-hidden border border-zinc-800 mx-auto flex">
<img id="twSumImage" src="" class="w-24 h-24 object-cover flex-shrink-0 hidden">
<div class="p-3 flex flex-col justify-center min-w-0">
<div id="twSumDomain" class="text-xs text-zinc-500"></div>
<div id="twSumTitle" class="font-bold text-sm mt-0.5 truncate"></div>
<div id="twSumDesc" class="text-xs text-zinc-400 mt-0.5 line-clamp-2"></div>
</div>
</div>
</div>
<!-- Discord -->
<div class="bg-slate-800 p-6 rounded-lg shadow-md">
<h2 class="text-xl font-bold mb-4 text-indigo-400 border-b border-slate-700 pb-2">Discord</h2>
<div class="preview-card bg-[#313338] text-[#dbdee1] rounded-md overflow-hidden mx-auto discord-card">
<div class="p-3 flex gap-3">
<img id="discordImage" src="" class="w-20 h-20 rounded object-cover flex-shrink-0 hidden">
<div class="min-w-0">
<div id="discordSiteName" class="text-xs text-[#5865F2] font-semibold truncate"></div>
<div id="discordTitle" class="font-bold text-sm mt-1 truncate"></div>
<div id="discordDesc" class="text-xs text-[#949ba4] mt-1 line-clamp-2"></div>
</div>
</div>
</div>
</div>
<!-- Telegram / WhatsApp -->
<div class="bg-slate-800 p-6 rounded-lg shadow-md">
<h2 class="text-xl font-bold mb-4 text-emerald-400 border-b border-slate-700 pb-2">Telegram / WhatsApp</h2>
<div class="preview-card bg-white text-black rounded-lg overflow-hidden border border-gray-200 shadow-sm mx-auto">
<img id="telegramImage" src="" class="w-full h-48 object-cover hidden">
<div class="p-3">
<div id="telegramDomain" class="text-xs text-blue-500"></div>
<div id="telegramTitle" class="font-semibold text-gray-900 text-base mt-0.5 truncate"></div>
<div id="telegramDesc" class="text-sm text-gray-500 mt-0.5 line-clamp-2"></div>
</div>
</div>
</div>
</div>
<div class="bg-slate-800 p-6 rounded-lg shadow-md h-fit">
<h2 class="text-xl font-bold mb-4 text-emerald-400 border-b border-slate-700 pb-2">Detected Meta Tags</h2>
<div class="overflow-x-auto">
<table class="w-full text-left text-sm text-slate-300">
<thead>
<tr class="border-b border-slate-700 text-slate-400">
<th class="pb-2 w-1/3">Property / Name</th>
<th class="pb-2 w-2/3">Content</th>
</tr>
</thead>
<tbody id="metaTableBody" class="divide-y divide-slate-700/50">
<tr>
<td colspan="2" class="py-4 text-center text-slate-500">Enter a URL to see its meta tags</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<script>
async function analyzeMetaTags() {
const urlInput = document.getElementById('urlInput').value.trim();
const loading = document.getElementById('loading');
const tableBody = document.getElementById('metaTableBody');
loading.classList.remove('hidden');
tableBody.innerHTML = '';
let targetUrl = urlInput || window.location.origin + '/';
if (targetUrl.startsWith('/')) {
targetUrl = window.location.origin + targetUrl;
}
const isSameOrigin = targetUrl.startsWith(window.location.origin);
const isLocal = targetUrl.includes('localhost') || targetUrl.includes('127.0.0.1');
if (!isSameOrigin && !isLocal) {
targetUrl = `https://api.allorigins.win/get?url=${encodeURIComponent(targetUrl)}`;
}
try {
const response = await fetch(targetUrl, { cache: 'no-cache' });
if (!response.ok) throw new Error('Could not fetch the URL.');
let htmlText = '';
if (targetUrl.includes('allorigins.win')) {
const data = await response.json();
htmlText = data.contents;
} else {
htmlText = await response.text();
}
const parser = new DOMParser();
const doc = parser.parseFromString(htmlText, 'text/html');
const metaTags = doc.querySelectorAll('meta');
const data = {
title: doc.querySelector('title')?.innerText || '',
siteName: '',
ogTitle: '', ogDesc: '', ogImage: '',
twTitle: '', twDesc: '', twImage: '',
rawMetas: []
};
metaTags.forEach(meta => {
const name = meta.getAttribute('name') || meta.getAttribute('property');
const content = meta.getAttribute('content');
if (name && content) {
data.rawMetas.push({ name, content });
if (name === 'og:title') data.ogTitle = content;
if (name === 'og:description') data.ogDesc = content;
if (name === 'og:image') data.ogImage = content;
if (name === 'og:site_name') data.siteName = content;
if (name === 'twitter:title') data.twTitle = content;
if (name === 'twitter:description') data.twDesc = content;
if (name === 'twitter:image') data.twImage = content;
}
});
const fallbackDesc = doc.querySelector('meta[name="description"]')?.getAttribute('content') || '';
if (data.rawMetas.length === 0) {
tableBody.innerHTML = `<tr><td colspan="2" class="py-4 text-center text-yellow-500">No explicit meta tags found.</td></tr>`;
} else {
data.rawMetas.forEach(m => {
const row = `<tr class="hover:bg-slate-700/30">
<td class="py-2 pr-2 font-mono text-xs text-indigo-300 break-all">${escapeHtml(m.name)}</td>
<td class="py-2 font-sans break-all text-slate-200">${escapeHtml(m.content)}</td>
</tr>`;
tableBody.insertAdjacentHTML('beforeend', row);
});
}
let domain = '';
try { domain = new URL(urlInput || window.location.href).hostname; } catch(e) { domain = 'localhost'; }
// --- Open Graph ---
document.getElementById('ogDomain').innerText = domain;
document.getElementById('ogTitle').innerText = data.ogTitle || data.title || 'No title';
document.getElementById('ogDesc').innerText = data.ogDesc || fallbackDesc || 'No description available.';
updateImage('ogImage', data.ogImage, targetUrl);
// --- Twitter Large Image ---
document.getElementById('twDomain').innerText = domain;
document.getElementById('twTitle').innerText = data.twTitle || data.ogTitle || data.title || 'No title';
document.getElementById('twDesc').innerText = data.twDesc || data.ogDesc || fallbackDesc || 'No description.';
updateImage('twImage', data.twImage || data.ogImage, targetUrl);
// --- Twitter Summary ---
document.getElementById('twSumDomain').innerText = domain;
document.getElementById('twSumTitle').innerText = data.twTitle || data.ogTitle || data.title || 'No title';
document.getElementById('twSumDesc').innerText = data.twDesc || data.ogDesc || fallbackDesc || 'No description.';
updateImage('twSumImage', data.twImage || data.ogImage, targetUrl);
// --- Discord ---
document.getElementById('discordSiteName').innerText = data.siteName || domain;
document.getElementById('discordTitle').innerText = data.ogTitle || data.title || 'No title';
document.getElementById('discordDesc').innerText = data.ogDesc || fallbackDesc || 'No description.';
updateImage('discordImage', data.ogImage, targetUrl);
// --- Telegram / WhatsApp ---
document.getElementById('telegramDomain').innerText = domain;
document.getElementById('telegramTitle').innerText = data.ogTitle || data.title || 'No title';
document.getElementById('telegramDesc').innerText = data.ogDesc || fallbackDesc || 'No description.';
updateImage('telegramImage', data.ogImage, targetUrl);
} catch (error) {
console.error(error);
alert('Error scanning the URL. If testing localhost, make sure your local server allows CORS or serve this file from your own server.');
} finally {
loading.classList.add('hidden');
}
}
function updateImage(elementId, imgUrl, baseUrl) {
const imgEl = document.getElementById(elementId);
if (imgUrl) {
if (!imgUrl.startsWith('http://') && !imgUrl.startsWith('https://')) {
try {
imgUrl = new URL(imgUrl, baseUrl).href;
} catch(e){}
}
imgEl.src = imgUrl;
imgEl.classList.remove('hidden');
} else {
imgEl.classList.add('hidden');
}
}
function escapeHtml(str) {
return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment