-
Notifications
You must be signed in to change notification settings - Fork 91
Description
Implementing Link Unfurling in a React Application
Link unfurling (displaying rich previews of URLs) can be challenging in React due to CORS restrictions and the need to fetch metadata from external websites. Here's a structured approach that aligns with modern React architecture and your existing codebase.
1. Server-Side Proxy for Metadata Fetching
Since direct browser requests to external sites often face CORS issues, implement a server-side proxy:
// Server-side endpoint (Node.js example)
app.get('/api/unfurl', async (req, res) => {
try {
const { url } = req.query;
// Fetch metadata using libraries like open-graph-scraper, metascraper, etc.
const metadata = await fetchMetadata(url);
res.json(metadata);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
2. Create a React Component for Link Previews
You can build a LinkPreview component using Material-UI and integrate it with components like Redirect and UriResolver.
import * as React from 'react';
import * as M from '@material-ui/core';
const useStyles = M.makeStyles((t) => ({
container: {
border: 1px solid ${t.palette.divider},
borderRadius: t.shape.borderRadius,
padding: t.spacing(2),
marginTop: t.spacing(1),
marginBottom: t.spacing(1),
},
image: {
maxWidth: '100%',
maxHeight: '200px',
objectFit: 'cover',
},
title: {
fontWeight: 'bold',
},
description: {
color: t.palette.text.secondary,
},
}));
export default function LinkPreview({ url }) {
const classes = useStyles();
const [metadata, setMetadata] = React.useState(null);
const [loading, setLoading] = React.useState(false);
const [error, setError] = React.useState(null);
React.useEffect(() => {
if (!url) return;
setLoading(true);
fetch(/api/unfurl?url=${encodeURIComponent(url)})
.then(res => res.json())
.then(data => {
setMetadata(data);
setLoading(false);
})
.catch(err => {
setError(err.message);
setLoading(false);
});
}, [url]);
if (loading) return <M.CircularProgress size={24} />;
if (error) return <M.Typography color="error">Failed to load preview: {error}</M.Typography>;
if (!metadata) return null;
return (
<M.Paper className={classes.container} variant="outlined">
{metadata.image && (
)}
<M.Typography variant="h6" className={classes.title}>
{metadata.title || 'No title'}
</M.Typography>
<M.Typography variant="body2" className={classes.description}>
{metadata.description || 'No description available'}
</M.Typography>
<M.Typography variant="caption" color="textSecondary">
{new URL(url).hostname}
</M.Typography>
</M.Paper>
);
}
3. URL Detection in Text
Automatically detect and preview links inside text blocks:
function detectAndRenderLinks(text) {
const urlRegex = /(https?://[^\s]+)/g;
const parts = text.split(urlRegex);
return parts.map((part, i) => {
if (part.match(urlRegex)) {
return ;
}
return part;
});
}
4. Integration with Existing Components
You can embed previews directly into editors or markdown renderers.
function EnhancedEditor({ value, onChange }) {
const [previewUrl, setPreviewUrl] = React.useState(null);
const handleChange = (e) => {
const newValue = e.target.value;
onChange(newValue);
const words = newValue.split(/\s+/);
const lastWord = words[words.length - 1];
if (lastWord.match(/^https?:///)) {
setPreviewUrl(lastWord);
}
};
return (
<M.TextField
fullWidth
multiline
value={value}
onChange={handleChange}
/>
{previewUrl && }
);
}
Handling Edge Cases
- Caching: Cache previews server-side to reduce load and latency
- Fallbacks: Gracefully degrade when metadata is missing
- Security: Sanitize all user-rendered metadata to avoid XSS
- Rate Limiting: Protect your proxy endpoint with rate limiting
Alternative Approaches
- Use a third-party service like iframely, embedly, or microlink.io
- Use Web Workers to offload link processing
- Leverage React Server Components (if using React 18+) to pre-render previews
This solution integrates cleanly with your architecture and complements components like Redirect, UriResolver, and your Material-UI styling system.