DEV Community

Cover image for How to build simple link preview without any external LIBRARY in react
Rahul Solanki
Rahul Solanki

Posted on

How to build simple link preview without any external LIBRARY in react

You must have encounter situation where you want to want to preview the link and for that you have to use external libraries. Now external libraries have their own advantages. But sometimes we just want our link preview component to be very simple and we don't want to fall into much complicated steps of using external libraries for example building our personal portfolio where we have to showcase our blogs that are published on different platforms.

Step1: Define preview componant

function LinkPreview({ url }) {
  // State to hold the link preview data and loading status
  const [previewData, setPreviewData] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    // Fetches the link preview data when the URL prop changes
    const fetchData = async () => {
      try {
        const response = await fetch(url);
        const data = await response.text();

        // Parsing logic to extract link preview data goes here

        setLoading(false);
      } catch (error) {
        console.error(error);
        setLoading(false);
      }
    };

    fetchData();
  }, [url]);

  // Rendering logic for the link preview goes here

  return (
    // JSX code for rendering the link preview component
  );
}

export default LinkPreview;

Enter fullscreen mode Exit fullscreen mode

above code will take url as parameter and later process it. The previewData state will hold the link preview data, and the loading state will track the loading status while fetching the data.Next, we use the useEffect hook to fetch the link preview data when the url prop changes. Inside the fetchData function, we use the fetch function to make a request to the provided URL. Once we have the response, we extract the necessary data, such as title, description, and image, from the HTML response which you have seen written in head section of HTML code.

Step2: Extracting Link Preview Data

To extract the link preview data, we need to parse the HTML response and retrieve the relevant information. In this step, we will extract the title, description, and image using a basic parsing technique.

const parser = new DOMParser();
const doc = parser.parseFromString(data, 'text/html');
const title = doc.querySelector('title')?.textContent || '';
const description = doc.querySelector('meta[name="description"]')?.getAttribute('content') || '';
const image = doc.querySelector('meta[property="og:image"]')?.getAttribute('content') || '';

setPreviewData({ title, description, image });

Enter fullscreen mode Exit fullscreen mode

It will create a new instance of the DOMParser and parse the HTML data using 'parser.parseFromString(data, 'text/html')', where data is the HTML response.
We will use 'doc.querySelector' to search for the required elements within the parsed HTML document.

Finally, we will update the previewData state with the extracted information by calling 'setPreviewData({ title, description, image })'.

Step3: Rendering the Link Preview

Now that we have the link preview data available, let's update the rendering logic in the return statement:

return (
  <div>
    <h3>{previewData.title}</h3>
    <p>{previewData.description}</p>
    {previewData.image && <img src={previewData.image} alt="Link Preview" />}
  </div>
);

Enter fullscreen mode Exit fullscreen mode

You can style the componant according to your choice, I am not doing because i sucks at designing. and that's all for the very simple link preview component.

Final result-

Now we can implement some error and loading state and with that we can also implement the redirect to website according to the link provided.

import { useState, useEffect } from 'react';

function LinkPreview({ url }) {
  const [previewData, setPreviewData] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await fetch(url);
        const data = await response.text();
        const parser = new DOMParser();
        const doc = parser.parseFromString(data, 'text/html');
        const title = doc.querySelector('title')?.textContent || '';
        const description = doc.querySelector('meta[name="description"]')?.getAttribute('content') || '';
        const image = doc.querySelector('meta[property="og:image"]')?.getAttribute('content') || '';

        setPreviewData({ title, description, image });
        setLoading(false);
      } catch (error) {
        console.error(error);
        setLoading(false);
      }
    };

    fetchData();
  }, [url]);

  if (loading) {
    return <p>Loading...</p>;
  }

  if (!previewData) {
    return <p>Failed to fetch link preview.</p>;
  }

  const handleClick = () => {
    window.open(url, '_blank');
  };

  return (
    <div onClick={handleClick} style={{ cursor: 'pointer' }}>
      <h3>{previewData.title}</h3>
      <p>{previewData.description}</p>
      {previewData.image && <img src={previewData.image} alt="Link Preview" />}
    </div>
  );
}

export default LinkPreview;

Enter fullscreen mode Exit fullscreen mode

But what if it has a youtube video. The above code definately will not show the desired result. That what can we do?
well here is an escape:
We can check if a url is related to youtube or not and then using some methods we can extract the thumbnail of video for image section in link preview.

import { useState, useEffect } from 'react';

function LinkPreview({ url }) {
  const [previewData, setPreviewData] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await fetch(url);
        const data = await response.text();

        const isYouTubeVideo = isYouTubeURL(url);
        if (isYouTubeVideo) {
          const videoId = extractYouTubeVideoId(url);
          const videoThumbnail = `https://img.youtube.com/vi/${videoId}/maxresdefault.jpg`;

          setPreviewData({
            videoId,
            videoThumbnail,
          });
          setLoading(false);
        } else {
          const parser = new DOMParser();
          const doc = parser.parseFromString(data, 'text/html');
          const title = doc.querySelector('title')?.textContent || '';
          const description = doc.querySelector('meta[name="description"]')?.getAttribute('content') || '';
          const image = doc.querySelector('meta[property="og:image"]')?.getAttribute('content') || '';

          setPreviewData({
            title,
            description,
            image,
          });
          setLoading(false);
        }
      } catch (error) {
        console.error(error);
        setLoading(false);
      }
    };

    fetchData();
  }, [url]);

  const isYouTubeURL = (url) => {
    return url.includes('youtube.com') || url.includes('youtu.be');
  };

  const extractYouTubeVideoId = (url) => {
    const videoIdRegex = /(?:\/embed\/|\/watch\?v=|\/(?:embed\/|v\/|watch\?.*v=|youtu\.be\/|embed\/|v=))([^&?#]+)/;
    const match = url.match(videoIdRegex);
    return match ? match[1] : '';
  };

  if (loading) {
    return <p>Loading...</p>;
  }

  if (!previewData) {
    return <p>Failed to fetch link preview.</p>;
  }

  const handleClick = () => {
    window.open(url, '_blank');
  };

  if (previewData.videoId) {
    return (
      <div onClick={handleClick} style={{ cursor: 'pointer' }}>
        <img src={previewData.videoThumbnail} alt="Video Thumbnail" />
      </div>
    );
  }

  return (
    <div onClick={handleClick} style={{ cursor: 'pointer' }}>
      <h3>{previewData.title}</h3>
      <p>{previewData.description}</p>
      {previewData.image && <img src={previewData.image} alt="Link Preview" />}
    </div>
  );
}

export default LinkPreview;

Enter fullscreen mode Exit fullscreen mode

'isYoutubeUrl' function

It will check if youtube keyword is in there to sperate youtube link and normal link

'extractYouTubeVideoId' function-

It will be reponsible for extracting video ID,
The pattern
(?:\/embed\/|\/watch\?v=|\/(?:embed\/|v\/|watch\?.*v=|youtu\.be\/|embed\/|v=))([^&?#]+)
is used to match various YouTube URL formats, including 'embed', 'watch?v=, v/', 'watch?...v=', and 'youtu.be/'. The captured group ([^&?#]+) captures the video ID.
We then use the match method on the url string with the videoIdRegex to search for a match. If a match is found, the video ID is extracted using match[1]. If no match is found, the function returns an empty string.

And after finding we will get the thumbnail image easily.

and that's it.

Follow me on twitter - @Rahulj9a

Top comments (6)

Collapse
 
artydev profile image
artydev • Edited

Nice Idea :-)
Perhaps you could update the title of your post, and specify you use React ?
Thank you

Collapse
 
rahulj9a profile image
Rahul Solanki

Ya! i did that, i realise that after posting 😅

Collapse
 
artydev profile image
artydev • Edited

:-)
Challenge for you, try to do the same with vanjs.org/ an hundred times lighter than React, no bundler, directly in browser...🙂

Collapse
 
elanza48 profile image
Elanza-48 • Edited

It seems very simple but not effective. Because browser blocks response if cors header is not same as the origin domain. So the success rate of the fetch request is very low. You have no choice but to impliment this in backend

Collapse
 
mauriblint profile image
Mauri Blint

Hello, in case is useful for someone, I created a public API to extract metadata from any link, you can just call the endpoint, and build/display the preview.

Collapse
 
ywroh profile image
ywroh

thank you 😊