Skip to content

Implement Link Unfurling #4378

@drernie

Description

@drernie

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 && (
          {metadata.title}
        )}
        <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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions