Embedding external content in blog posts can feel clunky — a raw link, a screenshot, or worse, an embedded iframe that kills your page performance. I built three new components that auto-fetch everything and render beautiful, consistent previews for the things you link to most: YouTube videos, GitHub repos, and websites.
All three match your site's design system, require no manual configuration, and use only client-side fetching.
YouTube Videos
Embed a YouTube video preview by just passing the video ID. Everything else is automatic.
The component auto-fetches the thumbnail from YouTube's CDN. The play button scales on hover, and there's a subtle background overlay.
<YoutubePreview videoId="dQw4w9WgXcQ" />
You can optionally provide title and channel:
<YoutubePreview
videoId="dQw4w9WgXcQ"
title="Never Gonna Give You Up"
channel="Rick Astley"
/>
GitHub Repositories & Profiles
Link to a repo, user profile, or organization — the component auto-fetches everything from GitHub's API:
User profile:
Organization:
It fetches:
- Repository previews: name, description, stars, forks, language, owner avatar
- User/org previews: name, bio, followers, public repos, avatar
No props needed except the URL. The component handles all the fetching and formatting.
<GithubPreview url="https://github.com/vercel/next.js" />
<GithubPreview url="https://github.com/torvalds" />
<GithubPreview url="https://github.com/vercel" />
Website Previews
Embed a preview of any website by just passing the URL. Everything is auto-fetched:
It fetches:
- Title from Open Graph meta tag or page title
- Description from Open Graph or meta description tag
- OG image (the preview image you see when shared on social media)
- Favicon from the website's head tag
- Domain extracted from the URL
One prop, unlimited data. No manual configuration needed.
<WebsitePreview url="https://github.com" />
<WebsitePreview url="https://nextjs.org" />
Styling & Design
All three components follow your site's design language:
- Minimal API — most only require the URL, everything else is fetched
- Zero configuration — no manual title, description, or image props needed
- Smart fallbacks — gracefully handles missing metadata
- Smooth animations — hover effects on screenshots and links
- Responsive design — works on mobile and desktop
- Consistent styling — Tailwind CSS v4 semantic tokens
The components use useEffect to fetch data client-side and show loading skeletons while fetching. The server APIs cache responses for 1 hour with stale-while-revalidate for freshness.
How it works
When you use a preview component:
- Client component mounts and shows a loading skeleton
- Fetches from API (
/api/github/previewor/api/website/preview) - API fetches source (GitHub API or website HTML)
- Parses metadata (description, image, stats, etc.)
- Returns cached response (1 hour cache with 24-hour stale-while-revalidate)
- Component renders with all data automatically populated
No need to provide title, description, stars, forks, or images — they're all fetched automatically.
Import & Usage
All three are exported from @/components/previews:
import {
YoutubePreview,
GithubPreview,
WebsitePreview,
} from "@/components/previews";
Drop them directly into any .mdx blog post. No frontmatter changes needed.
Component Props
videoId(required) — YouTube video ID (e.g., "dQw4w9WgXcQ")title(optional) — Override the fetched titlechannel(optional) — Override the channel nameclassName— Additional Tailwind classes
url(required) — Full GitHub URL (repo, user, or org)className— Additional Tailwind classes
Auto-fetches: name, description, avatar, stars, forks, language, follower count
url(required) — Website URLclassName— Additional Tailwind classes
Auto-fetches: title, description, OG image, favicon
