Rich link previews make any app feel more polished. When a user shares a URL, showing the page title, description, and preview image turns a bare link into an engaging preview card.
In this guide, we'll build a link preview system from scratch — and show how to use a metadata API to skip the hard parts.
A great link preview has four elements:
og:image)The traditional approach: fetch the HTML and parse meta tags.
async function getLinkPreview(url) {
const response = await fetch(url);
const html = await response.text();
// Extract title
const titleMatch = html.match(/<title>(.*?)<\/title>/);
// Extract OG image
const ogImageMatch = html.match(
/<meta\s+property="og:image"\s+content="([^"]*)"/
);
// Extract description
const descMatch = html.match(
/<meta\s+name="description"\s+content="([^"]*)"/
);
return {
title: titleMatch?.[1] || null,
image: ogImageMatch?.[1] || null,
description: descMatch?.[1] || null,
};
}
Problems with this approach:
For modern SPAs, you need a headless browser:
import puppeteer from 'puppeteer';
async function getLinkPreview(url) {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto(url, { waitUntil: 'networkidle2' });
const metadata = await page.evaluate(() => {
const getMeta = (property) => {
const el = document.querySelector(
`meta[property="og:${property}"], meta[name="twitter:${property}"]`
);
return el?.getAttribute('content') || null;
};
return {
title: getMeta('title') || document.title,
description: getMeta('description'),
image: getMeta('image'),
favicon: document.querySelector('link[rel="icon"]')?.href || '/favicon.ico',
};
});
await browser.close();
return metadata;
}
This works better, but now you're managing headless browser infrastructure — high RAM usage, slow cold starts, and complex scaling.
A dedicated API eliminates the infrastructure complexity:
async function getLinkPreview(url) {
const response = await fetch('https://api.brandohue.com/api/v1/extract', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.BRANDOHUE_API_KEY}`,
},
body: JSON.stringify({ url }),
});
const data = await response.json();
return {
title: data.ogTitle || data.title,
description: data.ogDescription || data.description,
image: data.ogImage,
favicon: data.faviconUrl,
domain: new URL(url).hostname,
};
}
One API call gives you:
ogTitle and ogDescription — Open Graph dataogImage — The preview imagetwitterCard — Twitter card type and imagefaviconUrl — The domain's faviconlogoUrl — The company's logo (bonus)Now let's build the UI component:
function LinkPreview({ url }) {
const [preview, setPreview] = useState(null);
useEffect(() => {
getLinkPreview(url).then(setPreview);
}, [url]);
if (!preview) return <LinkSkeleton />;
return (
<div className="link-preview-card">
{preview.image && (
<img src={preview.image} alt="" className="preview-image" />
)}
<div className="preview-content">
<div className="preview-domain">
<img src={preview.favicon} alt="" className="favicon" />
{preview.domain}
</div>
<h3 className="preview-title">{preview.title}</h3>
<p className="preview-description">{preview.description}</p>
</div>
</div>
);
}
A production link preview system needs to handle:
Brandohue handles all of these automatically — including built-in caching that returns cached data in under 100ms.
app.event('link_shared', async ({ event, client }) => {
for (const link of event.links) {
const preview = await getLinkPreview(link.url);
await client.chat.unfurl({
channel: event.channel,
ts: event.message_ts,
unfurls: {
[link.url]: {
title: preview.title,
text: preview.description,
image_url: preview.image,
},
},
});
}
});
const preview = await getLinkPreview(url);
const embed = {
title: preview.title,
description: preview.description,
image: { url: preview.image },
footer: { text: preview.domain, icon_url: preview.favicon },
};
Building link previews from scratch requires parsing HTML, handling JS rendering, managing browser infrastructure, and dealing with edge cases. A metadata API like Brandohue gives you all the preview data you need in one call — with built-in JavaScript rendering, caching, and error handling.
Start building better link previews with 1000 free credits.