-
Notifications
You must be signed in to change notification settings - Fork 0
Link Previews
github-actions[bot] edited this page Sep 2, 2026
·
46 revisions
Complete implementation of rich link previews with Open Graph metadata extraction, Twitter Cards, and platform-specific handlers.
- β Automatic URL detection in message content
- β Extracts up to 2-3 URLs per message (configurable)
- β Real-time preview fetching as messages are sent
- β Smart URL pattern matching
- β Open Graph protocol support (title, description, images, videos)
- β Twitter Card metadata
- β Fallback to basic HTML meta tags
- β Favicon extraction
- β Theme color detection
- β Author and publication date
- β YouTube video previews with thumbnails
- β GitHub repository and issue/PR previews
- β Twitter/X post previews
- β Spotify track/album/playlist previews
- β Code snippet previews (Gist, CodePen, CodeSandbox)
- β Direct image and video URL handling
- β SSRF protection (blocks private IPs and localhost)
- β Rate limiting per domain (10 requests/minute)
- β Client rate limiting (30 requests/minute)
- β Server-side caching (1 hour TTL)
- β Request timeout (10 seconds)
- β Response size limits (5MB max)
- β Blocked ports protection
- β Privacy mode to hide/show previews
- β Dismiss individual previews
- β Disable auto-unfurl option
- β Loading states with skeletons
- β Error handling with fallback display
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Message Component β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β MessageContent β
β βββ Text Content β
β βββ LinkPreview Component β
β βββ Auto-detect URLs β
β βββ Fetch previews via API β
β βββ Display LinkPreviewCard(s) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β API Route (/api/unfurl) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β 1. Rate Limiting β
β 2. URL Validation & SSRF Check β
β 3. Cache Check β
β 4. Fetch URL with timeout β
β 5. Parse HTML β
β 6. Extract Metadata β
β 7. Cache Result β
β 8. Return Preview Data β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β URL Unfurler Service β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β unfurlUrl() β
β βββ Security checks (SSRF, blocked domains) β
β βββ Rate limit enforcement β
β βββ Cache management β
β βββ HTTP fetch with protection β
β βββ HTML parsing β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Parser Services β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Open Graph Parser β
β βββ parseOpenGraph() - Extract og: meta tags β
β Twitter Card Parser β
β βββ parseTwitterCard() - Extract twitter: meta tags β
β Basic Metadata Parser β
β βββ parseBasicMetadata() - Extract <title>, etc. β
β Domain Handlers β
β βββ YouTube handler β
β βββ GitHub handler β
β βββ Twitter handler β
β βββ Spotify handler β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
src/
βββ components/chat/
β βββ LinkPreview.tsx # Main preview component
β βββ link-preview-card.tsx # (Integrated in LinkPreview.tsx)
β βββ message-content.tsx # Updated with preview support
β
βββ lib/
β βββ link-preview/
β β βββ preview-types.ts # TypeScript types
β β βββ unfurl.ts # Main unfurl logic
β β βββ og-parser.ts # Open Graph parser
β β βββ twitter-parser.ts # Twitter Card parser
β β βββ domain-handlers.ts # Platform-specific handlers
β β βββ preview-sanitizer.ts # Security sanitization
β β βββ preview-cache.ts # Cache management
β β βββ index.ts # Public API
β β
β βββ social/
β β βββ url-unfurler.ts # Server-side unfurl service
β β βββ og-parser.ts # Complete OG implementation
β β
β βββ messages/
β βββ link-preview.ts # Client-side helpers
β
βββ app/api/
βββ unfurl/
β βββ route.ts # Main unfurl API endpoint
βββ link-preview/
βββ route.ts # Alternative endpoint
The LinkPreview component automatically detects and displays previews for URLs in messages:
import { MessageContent } from '@/components/chat/message-content'
;<MessageContent
content="Check out this article: https://example.com/article"
showLinkPreviews={true} // Default: true
/>You can also render previews manually:
import { LinkPreview } from '@/components/chat/LinkPreview'
;<LinkPreview
content="https://github.com/vercel/next.js"
maxPreviews={3}
autoFetch={true}
allowDismiss={true}
onDismiss={(url) => console.log('Dismissed:', url)}
/>Fetch preview data directly:
// Single URL
const response = await fetch('/api/unfurl?url=' + encodeURIComponent(url))
const result = await response.json()
if (result.success) {
console.log('Title:', result.data.title)
console.log('Image:', result.data.image)
console.log('Description:', result.data.description)
}
// Batch URLs
const response = await fetch('/api/unfurl', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
urls: ['https://example.com', 'https://github.com/vercel/next.js'],
forceRefresh: false,
}),
})
const result = await response.json()
// result.results contains a map of URL -> preview dataUse the unfurler service directly in API routes or server components:
import { unfurlUrl } from '@/lib/social/url-unfurler'
const result = await unfurlUrl('https://example.com', {
forceRefresh: false,
timeout: 10000,
cacheTtl: 3600000, // 1 hour
})
if (result.success) {
console.log('Preview:', result.data)
}# Optional: Custom user agent
UNFURL_USER_AGENT="nchat-bot/1.0 (+https://nchat.app/bot)"
# Optional: Custom timeout (milliseconds)
UNFURL_TIMEOUT=10000
# Optional: Cache duration (milliseconds)
UNFURL_CACHE_DURATION=3600000Default limits (configurable in code):
// Per domain
RATE_LIMIT_PER_DOMAIN: 10 requests/minute
// Per client (IP)
CLIENT_RATE_LIMIT: 30 requests/minuteAutomatically blocks:
- Private IP ranges (10.x.x.x, 172.16-31.x.x, 192.168.x.x)
- Loopback addresses (127.x.x.x, ::1)
- Link-local addresses (169.254.x.x)
- Cloud metadata services
- Blocked ports (22, 23, 25, 3389, 5432, 3306, 6379, 27017)
interface LinkPreviewData {
// Core fields
url: string
type: 'generic' | 'article' | 'video' | 'image' | 'audio' | ...
status: 'pending' | 'loading' | 'success' | 'error'
domain: string
isSecure: boolean
// Content
title?: string
description?: string
siteName?: string
favicon?: string
// Media
image?: string
imageWidth?: number
imageHeight?: number
imageAlt?: string
video?: string
audio?: string
// Metadata
author?: string
publishedTime?: string
modifiedTime?: string
locale?: string
themeColor?: string
// Cache
fetchedAt: number
expiresAt: number
// Error
error?: {
code: string
message: string
retryable: boolean
}
}- Video ID extraction
- Thumbnail URLs (hqdefault)
- Embed URLs
- Channel information (when available)
- Repository previews with stars/forks
- Issue and PR previews with state
- User/organization information
- OpenGraph images from GitHub CDN
- Tweet ID and author extraction
- oEmbed support
- User verification badges
- Media attachments
- Track, album, playlist, artist previews
- Embed URLs for playback
- Album artwork
- Artist information
- GitHub Gist
- CodePen
- CodeSandbox
- Embed URLs for live preview
The system handles various error conditions gracefully:
- Timeout (10 second limit)
- DNS resolution failures
- Connection refused
- SSL/TLS errors
- Response too large (5MB limit)
- Non-HTML content type
- Missing metadata
- Malformed HTML
- Private IP addresses
- Blocked domains
- SSRF attempts
- Blocked ports
- Per-domain rate limits
- Per-client rate limits
- Automatic retry-after headers
- In-memory cache (use Redis in production)
- 1 hour default TTL
- LRU eviction (max 1000 entries)
- Automatic cache size management
- Browser cache via Cache-Control headers
- 1 hour max-age
- Stale-while-revalidate for 24 hours
- Per-message preview caching
- Concurrent preview fetching (max 5 parallel)
- Lazy image loading
- Progressive preview rendering
- Cache hits avoid network requests
- Average fetch time: 200-500ms
- Cache hit rate: ~80% (typical)
- Memory usage: ~10KB per cached preview
- Network bandwidth: ~20-50KB per preview
# Test single URL
curl "http://localhost:3000/api/unfurl?url=https://github.com/vercel/next.js"
# Test with force refresh
curl "http://localhost:3000/api/unfurl?url=https://example.com&refresh=true"
# Test batch
curl -X POST http://localhost:3000/api/unfurl \
-H "Content-Type: application/json" \
-d '{"urls": ["https://github.com/vercel/next.js", "https://youtube.com/watch?v=dQw4w9WgXcQ"]}'import { unfurlUrl } from '@/lib/social/url-unfurler'
import { parseOpenGraph } from '@/lib/social/og-parser'
describe('URL Unfurler', () => {
it('should unfurl a valid URL', async () => {
const result = await unfurlUrl('https://example.com')
expect(result.success).toBe(true)
expect(result.data).toBeDefined()
})
it('should block private IPs', async () => {
const result = await unfurlUrl('http://192.168.1.1')
expect(result.success).toBe(false)
expect(result.errorCode).toBe('BLOCKED')
})
})- Replace in-memory cache with Redis
- Use CDN for preview images
- Implement distributed rate limiting
- Add monitoring and logging
- Regular security audits
- Update SSRF protection rules
- Monitor for abuse patterns
- Implement IP allowlists
- Enable HTTP/2 for parallel fetches
- Optimize image processing
- Implement preview pre-caching
- Use worker threads for parsing
- Check network tab for API errors
- Verify URL is accessible
- Check rate limits
- Inspect browser console for errors
- Verify URL is not private IP
- Check blocked domains list
- Ensure port is allowed
- Check cache hit rate
- Verify network latency
- Monitor API response times
- Check concurrent request limits
- Verify site has Open Graph tags
- Check robots.txt for bot blocking
- Verify User-Agent is allowed
- Test with different parsers
- oEmbed protocol support
- Video preview with inline player
- Audio waveform visualization
- PDF preview thumbnails
- RSS feed detection
- Schema.org structured data
- Machine learning for preview quality scoring
- WebSocket for real-time preview updates
- Preview history and analytics
- Custom domain handlers registry
nself-chat v0.3.0 | GitHub | Issues | Discussions | Demo
Edit this page | MIT License | Β© 2026
(See π Security section below for 2FA, PIN Lock, and security audits.)
(Search lives in π Reference below.)
- π¬ Advanced Messaging
- π E2EE Setup
- π Search Setup
- π Call Management
- πΊ Live Streaming
- π₯οΈ Screen Sharing
- πΉ Video Calling
- ποΈ Voice Calling
- π± Mobile Optimization
- π§ͺ Testing
- π i18n
- π API Overview
- π Complete Reference
- π» API Examples
- π€ Bot API
- π Auth API
- π GraphQL Schema
- π Deployment Overview
- π³ Docker
- βΈοΈ Kubernetes
- β Helm Charts
- β Production Checklist
- π Production Validation
- π’ Multi-Tenant
- ποΈ Architecture
- π Diagrams
- ποΈ Database Schema
- π Project Structure
- π TypeScript Types
- π SPORT Reference
- π 2FA
- π¬ Messaging
- π Call Management
- π Call State Machine
- π E2EE
- πΊ Live Streaming
- π± Mobile Calls
- π PIN Lock
- π Polls
- π₯οΈ Screen Sharing
- π Search
- π Social Media
- ποΈ Voice Calling
- π Security Overview
- π‘οΈ Security Audit
- β‘ Performance
- π Best Practices
- π 2FA
- π PIN Lock
- π E2EE
- π‘οΈ E2EE Audit
v1.0.0 β’ 2026