next.jsnext.js13mdxjs

Nextjs App Directory: MDX & Link doesn't work properly with relative links


I asked this question in the Next.js Github Issues, but didn't get any answer.

I copied the code from next.js/examples/app-dir-mdx. in the following codeSandbox - the only additional change I made was:

In the .mdx pages, I have added relative links to one of the other pages in the folder like below:

I added the customization for the anchor tag in mdx-components.tsx. But after the customization, the links are not being redirected properly.

Please note that the url in the DOM and even when I hover over the link show up as http://localhost:3001/blog/test

But what I noticed is that the url in the anchor tag href attribute is different when using app directory:


Please note that this isue is caused on when I override the default <a> element to use the next/link component. with the default<`> element, it works fine

I found this article which states that the next/link behavior is changed in Next 13.

Starting with Next.js 13, <Link> renders as <a>, so attempting to use <a> as a child is invalid.


Solution

  • This has been resolved in Nextjs 13.4.5.


    OLD

    Honestly, this looks like a bug in next/link when using Next 13 app router. <Link> component doesn't handle relative links correctly even when used in regular pages outside of mdx. I can only assume that Next MDX does some special processing of relative links, which breaks when you specify your custom link component.

    In the meantime, I came up with the following workaround.

    "use client";
    
    import * as React from "react";
    import Link from "next/link";
    import { usePathname } from "next/navigation";
    
    function absolute(pathname: string, href: string) {
      if (
        href.startsWith("http://") ||
        href.startsWith("https://") ||
        href.startsWith("/")
      ) {
        return href;
      }
    
      const stack = pathname.split("/");
      const parts = href.split("/");
      stack.pop();
    
      for (let i = 0; i < parts.length; i++) {
        if (parts[i] == ".") continue;
        if (parts[i] == "..") stack.pop();
        else stack.push(parts[i]);
      }
      return stack.join("/");
    }
    
    export default function Anchor({
      href,
      children,
    }: React.JSX.IntrinsicElements["a"]) {
      const pathname = usePathname();
      return href !== undefined ? (
        <Link href={absolute(pathname, href)}>{children}</Link>
      ) : null;
    }
    

    Hope this helps.