Every chat app, social feed, and CMS needs link previews. You paste a URL, and it shows the title, description, and image — like Slack, Discord, or Twitter.
Building this yourself means:
- Fetching remote pages server-side
- Parsing Open Graph / Twitter Card meta tags
- Handling timeouts, redirects, encodings
- Caching results
- Running a headless browser if you want screenshots
Or you can use an API and skip all of that. Here's how to add link previews to your app in under 5 minutes using LinkPeek — a free link preview API.
Step 1: Get a Free API Key
curl -X POST https://linkpeek-api.linkpeek.workers.dev/v1/register \
-H "Content-Type: application/json" \
-d '{"email": "you@example.com"}'
Response:
{
"api_key": "lp_aBcDeFgHiJkLmNoPqRsTuVwXyZ012345",
"plan": "free",
"daily_limit": 100
}
Free tier: 100 requests/day, no credit card.
Step 2: Fetch Link Metadata
Node.js / Express
// server.js
app.get('/api/preview', async (req, res) => {
const { url } = req.query;
const response = await fetch(
`https://linkpeek-api.linkpeek.workers.dev/v1/preview?url=${encodeURIComponent(url)}&key=${process.env.LINKPEEK_KEY}`
);
const data = await response.json();
res.json(data);
// Returns: { title, description, image, favicon, site_name, type }
});
Python / Flask
import requests
@app.route('/api/preview')
def preview():
url = request.args.get('url')
resp = requests.get(
'https://linkpeek-api.linkpeek.workers.dev/v1/preview',
params={'url': url, 'key': LINKPEEK_KEY}
)
return resp.json()
Step 3: Build the Preview Component
React
function LinkPreview({ url }) {
const [preview, setPreview] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch(`/api/preview?url=${encodeURIComponent(url)}`)
.then(res => res.json())
.then(data => { setPreview(data); setLoading(false); })
.catch(() => setLoading(false));
}, [url]);
if (loading) return <div className="preview-skeleton" />;
if (!preview) return null;
return (
<a href={url} className="link-preview" target="_blank" rel="noopener">
{preview.image && <img src={preview.image} alt="" />}
<div className="preview-text">
<h4>{preview.title}</h4>
<p>{preview.description}</p>
<span className="preview-domain">
{new URL(url).hostname}
</span>
</div>
</a>
);
}
CSS
.link-preview {
display: flex;
border: 1px solid #e5e7eb;
border-radius: 12px;
overflow: hidden;
text-decoration: none;
color: inherit;
max-width: 500px;
transition: box-shadow 0.2s;
}
.link-preview:hover {
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.link-preview img {
width: 120px;
height: 120px;
object-fit: cover;
}
.preview-text {
padding: 12px 16px;
flex: 1;
}
.preview-text h4 {
font-size: 15px;
margin: 0 0 4px;
line-height: 1.3;
}
.preview-text p {
font-size: 13px;
color: #6b7280;
margin: 0 0 8px;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.preview-domain {
font-size: 12px;
color: #9ca3af;
}
Alternative: Embed SVG Cards (Zero JavaScript)
If you just need a preview image, skip the component entirely:
<img
src="https://linkpeek-api.linkpeek.workers.dev/v1/preview/image?url=https://github.com&key=YOUR_KEY"
alt="Link preview"
width="600"
/>
This returns a styled SVG card — works in emails, READMEs, static sites, anywhere <img> tags work.
Step 4: Handle Edge Cases
function LinkPreview({ url }) {
const [preview, setPreview] = useState(null);
const [error, setError] = useState(false);
useEffect(() => {
const controller = new AbortController();
fetch(`/api/preview?url=${encodeURIComponent(url)}`, {
signal: controller.signal
})
.then(res => {
if (!res.ok) throw new Error('Failed');
return res.json();
})
.then(setPreview)
.catch(err => {
if (err.name !== 'AbortError') setError(true);
});
return () => controller.abort();
}, [url]);
if (error) return <a href={url}>{url}</a>;
if (!preview) return <div className="preview-skeleton" />;
return (/* same component as above */);
}
Key points:
- Abort controller — cancel in-flight requests on unmount
- Graceful fallback — show plain link if preview fails
- Server-side proxy — never expose your API key in client code
Response Format
Every /v1/preview call returns:
{
"url": "https://github.com",
"title": "GitHub: Let's build from here",
"description": "GitHub is where over 100 million developers...",
"image": "https://github.githubassets.com/assets/social.png",
"favicon": "https://github.githubassets.com/favicons/favicon.svg",
"site_name": "GitHub",
"type": "website"
}
Plus rate limit headers:
-
X-RateLimit-Limit— your daily limit -
X-RateLimit-Remaining— requests left today -
X-Cache— HIT or MISS
Pricing
| Plan | Requests/day | Price |
|---|---|---|
| Free | 100 | $0 |
| Pro | 5,000 | $9/mo |
| Business | 50,000 | $29/mo |
For comparison, Microlink starts at $20/mo and OpenGraph.io at $12/mo.
Try It Now
LinkPeek: https://linkpeek-api.linkpeek.workers.dev
The landing page has a live demo — try any URL without signing up. No credit card, no setup friction.
Full API docs: https://linkpeek-api.linkpeek.workers.dev/docs
What are you building that needs link previews? I'd love to hear about your use case in the comments.
Top comments (0)