Optimizing links in MDX using next/link
When building our navigation earlier we saw why it is beneficial to use next/link (for internal links) instead of regular HTML anchors, so now we want to do the same for all the markdown links that we will insert into our MDX pages, because without using the mdx-components the parser would just transform the markdown links into regular HTML anchors, we want markdown links to use next/link, which is what we will do next in our mdx-components.tsx
file
React component for external and internal links
First, we are going to create a custom Link component that will use next/link, next/link extends the HTML <a>
element to provide prefetching and client-side navigation between routes, and we are going to create a component that will use next/link for internal URLs and use a regular <a>
element for external URLs, we will also add an icon to external URLs to give users a visible hint that if they click on that link, they will leave our app
In the /components
folder, add a new base
folder
Then, in the base
folder, create a new Link.tsx
file and add the following code:
Line 1: we import the PropsWithChildren type from React, which we will use to strictly type our props interface
Line 2: we import the Link component from the next/link package
Line 3: we import the Route type from Next.js to strictly type the href variable in our props interface
Lines 5 to 10: we create an interface for the props of the component by extending the react type PropsWithChildren and using the Next.js Route type for the href property; we also add the types for some optional properties that might be useful when re-using the component
Lines 12 to 36: we create an isExternalUrl function that gets the URL for our link as input and then checks if the URL is internal or external; if the URL is external, it returns true; else, it returns false, I used a pretty naive approach to make the test, but that is because I didn't need more for my own URLs (if this is not complex enough for your own needs then feel free to improve that part, for example if you have more than one domain then you may want to use an array instead), for my use cases I just check if a URL starts with a slash (/
) or a hashtag (#
) which indicates it is an internal URL. I also check if a URL starts with http or https, in which case it is an external URL, except if the domain matches a value I have passed as a parameter to the function and which contains the domain name my website will use (in production)
Line 38: we use the interface we just created to strictly type the props of our functional React component
Lines 42: we use the isExternalUrl function to check if the URL is external or internal; the first parameter is the URL of the current link, and the second parameter is the domain of our project (replace example.com with the domain you intend to use in production)
Lines 44 to 49: we create a new link props object that will contain any original props that got passed to the component; then for (external domains) we set the values for two attributes rel and target; for rel we use the 2 values, noopener got introduced in HTML 5 to increase security and it is used to tell browsers if the link gets opened in a new window we don't want the website in that new window to be able to access our website by using the window.opener property, the second value noreferrer increases the privacy of our visitors by telling the browser not to set a referrer header (referrer information does NOT get leaked), which means the website that gets visited will not know the exact source; the target attribute we set it to _blank, this will instruct the browser to open the link in a new tab, which makes it easier for users to come back to our website by closing the new tab instead of having to navigate back one or more pages
Lines 53 to 67: in the return statement, we check if the link is external, in which case we use an HTML anchor element and add the external icon behind it, and if it is an internal link, then we use next/link
The SVG icon that I used is from icons.getbootstrap.com, they are MIT licensed, the icon I chose to use here is the Box arrow up-right icon
Using the BaseLink component for all markdown links
Next, open the mdx-components.tsx
(which is in the root of your project) again, and add a second mdx component to customize the anchor elements, like so:
Lines 2 to 3: we import our custom BaseLink component and the Route type from Next to strictly type the link href
Lines 19 to 23: we specify that for each markdown link that got transformed into an HTML anchor element, we want to use our BaseLink component to customize the link
External icon styling in the global.css
Next, we edit the global.css
file in the app
folder to add the styles for the SVG icon(s), like so:
Lines 126 to 133: we add some CSS to style our icon:
- we add some margin on the left of the icon to add some space between the icon and the preceding link
- we set the display property to inline-block to make sure the icon and the link are placed one after the other
- we set the fill property to tell the browser to use the currentcolor (which is the current color of our text) as filler color for our SVG icon
- we set the font-size and element height to the same value that is being used for our text (default font-size is 1rem) so that the icon matches the height of the link text
- finally, I manually adjusted the vertical-align value a little bit so that visually, the icon is centered vertically
Links examples using the MDX playground page
Finally, we update our playground page.mdx
file (that is in the /app/(tutorial_examples)/mdx-components_playground
folder) with some links examples to showcase internal and external links, like so:
Now launch the dev server (using the npm run dev
command) and then open the http://localhost:3000/mdx-components_playground
playground page URL in your browser to see the result
Congratulations 🎉 you just created a link component that will now get used for all markdown links, which means they will now all benefit from the next/link features like prefetching and optimized client-side navigation between routes
If you liked this post, please consider making a donation ❤️ as it will help me create more content and keep it free for everyone