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, Bing, Yahoo, Baidu and Yandex.
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).
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.
In this article 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 is a JavaScript library for building user interfaces. The most popular Toolchains to build a React app use Webpack 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).
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
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 without doubt.
Server-Side Rendering (SSR) technique make possible the idea to serve user interfaces using JavaScript from the server side. Next.js implements this technique, 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” feature that comes with Next.js. For experts developers in Next.js, of course this can be also achievable with “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!!!
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:
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.
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 :)
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.
5. Cloud function to get all the SEO data
As you see in the picture, we need to write a Cloud Function 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:
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 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 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 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.
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.
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.
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.
// 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.
// 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.
{
source: '/:path*',
destination: </span><span class="p">${</span><span class="nx">destinationHost</span><span class="p">}</span><span class="s2">/: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 for future references.
If you are wondering which is the App where I applied this solution, then register and explore into alldone.app. You will love it !!!
Happy coding!
Top comments (0)