Embedding external content in blog posts can feel clunky — a raw link, a screenshot, or a hand-rolled card that doesn't match the source platform. I built three components 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 by passing the video ID. The component renders YouTube's standard embedded player.
The component uses YouTube's official iframe embed format, so the preview and playback UI are handled by YouTube.
<YoutubePreview videoId="dQw4w9WgXcQ" />
You can optionally provide a title for the iframe:
<YoutubePreview videoId="dQw4w9WgXcQ" title="Never Gonna Give You Up" />
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
