This article was originally written on my personal blog here.
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.
Make sure you add canonical URLs to all of your pages in Remix app. Credits: Table flatlay photo created by freepik
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.
Even if I duplicated this post to Dev.to, I still get all the “SEO credits” to my website because of the canonical URL. Check out the canonical URL link the source of this page.
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,
}];
}
But TypeScript immediately turned me down and didn't accept having an object with data in parameters. It 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>
);
}
- First of all, we get the all matched routes using the useMatches utility hook
- We look for our pre-defined dynamicLinks function in exported handle constant of each matched route and call it giving the route data 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 at 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,
};
Note that this code assumes that I return canonicalUrl from my loader function.
And then I just need to render DynamicLinks the 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>
);
}
This results in having a dynamic canonical URL 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"/>
That’s it! I was upset at 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 have any questions please leave comments or tweet tagging me(at aibolik_).
Top comments (0)