DEV Community

eatyou eatyou
eatyou eatyou

Posted on

Add Rich Link Previews to Any Web App in 5 Minutes (React, Vue, Node.js)

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"}'
Enter fullscreen mode Exit fullscreen mode

Response:

{
  "api_key": "lp_aBcDeFgHiJkLmNoPqRsTuVwXyZ012345",
  "plan": "free",
  "daily_limit": 100
}
Enter fullscreen mode Exit fullscreen mode

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 }
});
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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"
/>
Enter fullscreen mode Exit fullscreen mode

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 */);
}
Enter fullscreen mode Exit fullscreen mode

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"
}
Enter fullscreen mode Exit fullscreen mode

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)