Link previews in React.js for Social Networks using Next.js
Tags: react, linkpreviews, nextjs, seo
Published: 2022-07-30 14:14:00

### SEO, Link Preview and React.js

As you may know, the easiest way to find a website or web application on the Internet is through online search engines like [Google](https://www.google.com/ncr), [Bing](https://www.bing.com/), [Yahoo](https://www.yahoo.com/), [Baidu](https://www.baidu.com/) and [Yandex](https://yandex.com/).

These systems, as part of their web page ranking algorithms, have established a list of parameters considered good practices for **Web Positioning (WP)** or **[Search Engine Optimization (SEO)](https://en.wikipedia.org/wiki/Search_engine_optimization)**.

In this way, the more parameters and the higher quality, the more chance your website has of being in the first positions of search engines.


### What is Link Preview?

As part of the new trends in web development, new applications offer the ability to share information on the network instantly. However, some time ago, despite having great network speed, **no one was able to know what a web page was about without first visiting it using a web client**.

**Link Preview** give us a way to preview the content summary of a link before click it. In this way we can determine if that resource is relevant to us or not.

![Link preview of Stack Overflow website](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/scb9zvxhwlp67io6obl9.png)

[In this article](https://medium.com/strategic-content-marketing/the-most-complete-link-preview-guide-698cc60920bc) you can get a more complete vision of what Link Preview is and how to debug the Link Previews of your web pages in the most popular social networks.


### Link Preview challenge in React.js

**[React.js](https://reactjs.org/)** is a JavaScript library for building user interfaces. The most popular [Toolchains to build a React](https://reactjs.org/docs/create-a-new-react-app.html#recommended-toolchains) app use [Webpack](https://webpack.js.org/) to bundle the JavaScript files. It means that at the end the React app will be transformed into a ready-to-deploy Vanilla JavaScript files.

When a search engine bot crawl a web page, it extracts the necessary information for the SEO. In the case of these bundled applications, it is presented like a simple HTML page that link a JavaScript file that, when it is executed, shows all the UI. These are usually called **[Single Page Applications (SPA)](https://en.wikipedia.org/wiki/Single-page_application)**.

The bots are not able to read -or execute- the JavaScript files to extract the information they need. In most cases these applications can have a generic SEO data in the index.html file, which is not practical if we built a corporate website with many pages using React.

Link Preview readers have exactly these limitations. So, at the end our solution will in most cases, **_“kill two birds in a shot”_**.


### Next.js and Server Side Rendering

![Next.js and Server-Side Rendering](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tjyh3dtcs2qja9wlskxf.png)

These days, if someone ask me about what to use to build a React application with a really good support for SEO, I would say **[Next.js](https://nextjs.org/)** without doubt.

**[Server-Side Rendering (SSR)](https://en.wikipedia.org/w/index.php?title=Server-side_rendering)** technique make possible the idea to serve user interfaces using JavaScript from the server side. [Next.js implements this technique](https://nextjs.org/docs/basic-features/pages#server-side-rendering), and make easier the way to obtain an awesome SEO support for our React app.


### Link Preview in React.js using Next.js

For this post, I will be using a real case sample that I face recently. I would say that the solution that I came up with was possible due to the tech stack we used in another related project and the third party services we used.

At some point **we (my team) had a SPA React.js app**. This app needed authentication to interact with all the views but, there was a **feature** that make it possible **to share internal views like if those were public pages** (without authentication).

So, **we wanted to have Link Preview for those publicly accessible views**. As you may guess, at that time it was “almost” impossible make it real with only React code.

Fortunately, **we choose Next.js to build the corporate Landing Page** of this app. And this was the gate to bring Link Previews to the light.

A little spoiler, **the trick was using the [“rewrites”](https://nextjs.org/docs/api-reference/next.config.js/rewrites)** feature that comes with Next.js. For experts developers in Next.js, of course this can be also achievable with [“middleware”](https://nextjs.org/docs/advanced-features/middleware), but unfortunately it didn’t work for me at that time, it was a very young feature.

---

If you like my work, then just **[Buy me a Coffee!!!](https://ko-fi.com/yulioaj290)**

---


## The solution …

As a first comment, is important to keep in mind that **this two apps live under the same domain**: the corporate Landing Page **(LP)** and the actual Product website **(App)**. For this example, let’s assume that our domain is **lighter.com**.


### 1. Identifying the route types

As a first step, we need to identify the groups of routes in our domain, no matter what app they belong to.

In our example, we have the **routes of the LP**, the routes for **static files** in the App, the pages of the App that we want **with Link Previews**, and the **rest of the App** routes.

Delimitation of these route groups will help us to define our re-write rules.


### 2. Starting the configuration

In the root folder of the Next.js app create the file `next.config.js` if you don’t have one already, with the following content in order to initialize the rewrite configuration:

```javascript
module.exports = {
    async rewrites() {
        return []
    },
}
```

### 3. Set rule for all Landing Page routes and static files

We need to define a **no-op rewrite** to trigger checking **all pages/static files from LP** before we attempt proxying anything else.

In other words, we are giving priority to the LP routes.

```javascript
module.exports = {
    async rewrites() {
        return [
            {
                source: "/:path*",
                destination: "/:path*",
            },
        ]
    },
}
```

### 4. Set rule for all React App static files

Now we need to **set rules for the static files** of the App, in order to just **redirect the HTTP request directly to them without any extra processing**.

You will understand later why we need to do this :)

```javascript
const destinationHost = "https://lighter.com"

module.exports = {
    async rewrites() {
        return [
            // All routes from the LP
            {
                source: "/:path*",
                destination: "/:path*",
            },            
            
            // Static files of the App
            {
                source: "/favicon.ico",
                destination: `${destinationHost}/favicon.ico`,
            },
            {
                source: "/static/:path*",
                destination: `${destinationHost}/static/:path*`,
            },
            {
                source: "/fonts/:path*",
                destination: `${destinationHost}/fonts/:path*`,
            },
        ]
    },
}
```

I used a `destinationHost` var here to interpolate the domain URL, this is a way of telling Next.js not to look for these resources in its files, it just redirects the request to the App.

---

From now, this is getting interesting!.

In Link Preview the SEO info needed will be taken always from the HTML code served by our Next.js when the third party systems requests the page to build the preview.

So, our mechanism to inject the SEO info to the App pages needs **something that tells him what is the SEO data to inject,** because different pages need different SEO info.

And this mechanism also needs **a way to serve the real pages dynamically built by the React App**, just after we inject the data for the previews.

With these ideas in mind, let’s see a brief picture of our algorithm, and then get our hands dirty with the rest of the code.


![Link Preview solution for React.js app using Next.js Rewrites](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gfweqsd43tnb1bgxyv1q.jpg)

---


### 5. Cloud function to get all the SEO data

As you see in the picture, we need to write a [Cloud Function](https://cloud.google.com/functions) that gives us the SEO data related to each route on demand.

The content of your function should vary depending on your app but here is an example:

```javascript
exports.getLinkPreviewData = functions
    .https.onRequest(async (req, res) => {
        const { pathname } = req.body.data
        const { title, description } = await processUrl(pathname)        

        // processUrl should compute the "title" and "description"
        // based on the pathname of the route        

        res.status(200).send({ title, description });
    })
```

The way we did it is by retrieving the information from a [Firebase](https://firebase.google.com/) DB based on the chunks of the pathname, which usually have the object type and the id of the resource.


### 6. Page route to inject SEO data

The next step is to create a [new page route](https://nextjs.org/docs/routing/introduction) to inject the SEO data.

Inside the `pages` folder, create a folder `_preview`, and inside it create the file `[...app_path].js`.

This [dynamic route](https://nextjs.org/docs/routing/dynamic-routes) will help us to handle generic routes under the `_preview` route prefix. This folder name is just for identification purpose, you can use whatever you want.

![Page route to inject the SEO data](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gtrzvb27x9j2lrjhzkf7.png)


```javascript
import Head from 'next/head'

export default function AppPath({ appPath, title, keywords, description }) {
    return (
        <Head>
            <title key="title">{title}</title>
            <meta key="keywords" name="keywords" content={`Lighter, Lighter.com, ${keywords}`} />
            <meta key="description" name="description" content={description} />
            <meta key="og-title" property="og:title" content={title} />
            <meta key="og-description" property="og:description" content={description} />
            <meta key="og-url" property="og:url" content={`https://lighter.com${appPath}`} />
            <meta key="twitter-title" name="twitter:title" content={title} />
            <meta key="twitter-description" name="twitter:description" content={description} />
            <meta name="twitter:card" content="summary_large_image" />
            <meta name="twitter:site" content="@lighter_app" />
            <meta name="twitter:image" content={`https://lighter.com/home-card.png`} />

            <link rel="canonical" href="https://lighter.com/" />
        </Head>
    )
}
```
 
This is the basic content of our component. It just receive the SEO data as props and interpolate them in all metadata inside the `head` tag.

To get the SEO data through props, we need to implement the `getServerSideProps` function from Next.js inside the component.


```javascript
export async function getServerSideProps(context) {
    const { url: urlPath } = context.req
    let previewData

    try {
        previewData = (
            await axios.post(`https://us-central1-lighter.cloudfunctions.net/getLinkPreviewData`, {
                data: { pathname: urlPath },
            })
        )?.data
    } catch (e) {
        console.log(e.message)
    }

    return {
        props: {
            appPath: urlPath,
            title: previewData?.title || 'Lighter App',
            keywords: 'Lighter, App game for kids, Multiplayer, Entertainment',
            description: previewData?.description || 'Lorem ipsum dolor sit amet consectetur ...',
        }, // will be passed to the page component as props
    }
}
```

In this function, we are calling the cloud function `getLinkPreviewData` that we wrote before, passing the current `pathname`. After this call, return the SEO data through the `props` object.

Finally, after mounting the component, we need to redirect to the same route but, **with a query parameter in the URL** that will say Next.js that this route has already the SEO data injected.


```javascript
export default function AppPath({ appPath, title, keywords, description }) {
    const router = useRouter()

    useEffect(() => {
        const { query } = router
        router.push({ pathname: `/${query['app_path'].join('/')}`, query: { from_landing: true } })
    }, [])

    return (
        <Head>
            //...
        </Head>
    )
}

```

Of course this code will not work alone, we need extra rewrite rules to handle this ;)


### 7. Catch all processed routes

Before defining the rules to handle the SEO data injection, we need first catch all routes already processed.

```javascript
// Redirect to the App the routes already processed                                         
{ 
    source: '/:path*',                                             
    has: [{ type: 'query', key: 'from_landing' }],                                             
    destination: `${destinationHost}/:path*`,                                       
},
```

Basically, we are asking if the parameter `from_landing` is present in the URL. If true, then redirect to the same route but with the `destinationHost` interpolated, which as said previously, redirects to the React App.


### 8. Inject SEO data into all routes of interest

To inject the SEO data, we just need to match all interested routes and route them to the preview route ( `[...app_path].js` ) we defined before.


```javascript
// Specific App router with Link Preview                                         
{
    source: '/board/:path*',
    destination: `/_preview/board/:path*`,
},
{
    source: '/battles/:path*',
    destination: `/_preview/battles/:path*`,
},
{
    source: '/maps/:path*',
    destination: `/_preview/maps/:path*`,
},
{
    source: '/character',
    destination: `/_preview/character`,
},
```

At this point, what occurs here is described in the step 6.


### 9. Rule for fallback routing

Finally, we need one last rule to cover all the rest of the App routes that don’t match with the routes we want to inject the SEO data.


```javascript
{
    source: '/:path*',
    destination: `${destinationHost}/:path*`,
},
```


## Conclusion…

In conclusion, Next.js has amazing features that make our life easier when dealing with this kind of technical problem that affects the development f our products.

Link Previews is one of the most desirable features we like in our web applications and it also means that we are optimizing our web pages to be easily indexed by the search engines.

With this example, maybe the next time you will be more comfortable solving this little issue in your development. And to prove my goodwill, you can find the entire code of this article [in this Gist](https://gist.github.com/yulioaj290/52c2b8f735e08529bf73abbd718fb0bd) for future references.

If you are wondering which is the App where I applied this solution, then register and explore into [alldone.app](https://alldone.app/). You will love it !!!

Happy coding!