I rewrote my blog using Remix.run several months ago, but I faced an issue with canonical links when I started improving the SEO of my blog. After some research about this, I came to a perfect solution that lets me have dynamic link elements that absorb route data. Let me show you how I got to it.
What is a canonical URL
If you already know what it is and why you need it, jump to the next section.
Simply put, a canonical URL is used by search engines to determine the origin of the content. Suppose your blog post is available through http://
, https://
and/or www.
URLs. In that case, it is highly recommended to put a canonical URL that follows some structure, so that search engines will mark other pages as duplicates and give all the credits to the original content.
Another popular reason to use canonical URLs is to syndicate your content. If you publish your post on your blog and third-party platforms(like medium.com, dev.to, etc.) Google will consider them as duplicate content. It might hurt the ranking of your website but using the canonical URLs you can turn this on its head and get back again all the SEO credits to your website by setting up canonical URLs in third-party platforms. This blog post is not meant to explain everything about canonical URLs, so if you still have questions I highly recommend reading about it here.
The problem with the links in Remix.run
Most likely, you have a template route like blog.$slug.tsx
in your Remix application. I thought I could add canonical URLs using a links
function, just like this(assuming I return canonicalUrl
in the route loader
):
export const links: LinksFunction = ({ data }) => { return [{ rel: 'canonical', href: data.canonicalUrl, }]; }
tsx
But TypeScript immediately turned me down and didn't accept having an object with data
in parameters. Turns out LinksFunction
does not have access to data returned by loader
and we can't put there any dynamic content.
Solution: use DynamicLinks
So after some research on the internet about adding dynamic link elements to a page in Remix, I came to a solution using DynamicLinks
. So, DynamicLinks
is not something that comes from the Remix.run documentation it is rather a utility that can be built using Remix.run capabilities.
Big thanks to Sergio who implemented it in remix-utils Open-Source library.
How does DynamicLinks
utility works
Let me show you the code and walk you through what is happening:
export function DynamicLinks() { let links: LinkDescriptor[] = useMatches().flatMap((match) => { let fn = match.handle?.dynamicLinks; if (typeof fn !== 'function') return []; return fn({ data: match.data }); }); return ( <React.Fragment> {links.map((link) => ( <link {...link} key={link.integrity || JSON.stringify(link)} /> ))} </React.Fragment> ); }
tsx
- First of all, we get the all matched routes using the
useMatches
utility hook - We look for our pre-defined
dynamicLinks
function in exportedhandle
constant of each matched route and call it giving the routedata
as a parameter(if the function is defined by the route) - After we collect links from all the matched routes, we simply render them
We can put this component in the root of the Remix app and it will work for all the routes that define dynamicLinks
function in exported handle
variable.
How to use DynamicLinks
in Remix app
The usage of the utility is very simple. I define dynamicLinks
function in my blog.$slug.tsx
module and export it within handle
Remix-defined constant:
const dynamicLinks: DynamicLinksFunction<LoaderData> = ({ data }) => { return [ { rel: 'canonical', href: data.canonicalUrl, }, ]; } export const handle = { dynamicLinks, };
tsx
Note that this code assumes that I return canonicalUrl
from my loader
function.
And then I just need to render DynamicLinks
component in the root of the Remix application:
import { DynamicLinks } from "remix-utils"; export default function App() { return ( <html lang="en"> <head> {/** your head tags */} <DynamicLinks /> </head> <body> {/** body tags */} <Outlet /> <Scripts /> </body> </html> ); }
tsx
This results in having a dynamic canonical URLs in all of my blog posts(both client and server-side). E.g.:
<link rel="canonical" href="https://aibolik.com/blog/how-to-add-dynamic-canonical-links-in-remix-application"/>
html
That's it! I was upset in the beginning that LinksFunction
does not support loader
data(for sure, for some good reason), but I liked how Remix gives the flexibility to implement this kind of utility. There are more examples in the Remix.run documentation about the useMatches
utility hook, like implementing breadcrumbs for all the nested components.
Thank you for reading this post. If you liked it make sure to share it with others.