next-mdx-remote: Breaking hyperlinks while mapping tag with MDXRemote

Hey!

The problem

I currently have an issue with MDXRemote + serialize with img inside mdxComponents

When I comment `img` mapping in `mdxComponents`, my hyperlinks are correct Screenshot 2021-07-31 at 16 32 34
When I add `img` mapping in `mdxComponents`, my hyperlinks are wrong, but only in the beginning of the blog post (usually, it's the second link 🤷‍♀️) Screenshot 2021-07-31 at 16 33 35

I want to understand, why?

More details

Technologies

Package.json – https://github.com/Beraliv/beraliv.dev/blob/main/packages/blog2/package.json#L12

Dependencies:

    "firebase-admin": "9.9.0",
    "formik": "2.2.9",
    "gray-matter": "4.0.3",
    "next": "11.0.1",
    "next-mdx-remote": "3.0.4",
    "react": "17.0.2",
    "react-dom": "17.0.2",
    "remark": "13.0.0",
    "remark-html": "13.0.1",
    "swr": "0.5.6",
    "unist-util-visit": "2.0.3",
    "yup": "0.32.9"

How I use them

I defined mdxComponents – https://github.com/Beraliv/beraliv.dev/blob/main/packages/blog2/src/components/mdx/index.ts:

export const mdxComponents = {
  // if I comment it, the problem with hyperlinks is gone
  img: MdxImage,
};

and use it later as PostBody – https://github.com/Beraliv/beraliv.dev/blob/main/packages/blog2/src/components/atoms/PostBody.tsx#L10:

export const PostBody: FC<PostBodyPropsType> = ({ content }) => (
  <MDXRemote {...content} components={mdxComponents} />
);

I get blog post content in getPostStaticProps this way – https://github.com/Beraliv/beraliv.dev/blob/main/packages/blog2/src/static/getPostStaticProps.ts#L10:

export const getPostStaticProps: GetStaticProps<
  PostPropsType,
  PostPropsParamsType
> = async ({ params }) => {
  if (!params?.slug) {
    throw new Error(`Cannot find slug for post ${location.href}`);
  }

  const { content, data } = getPostBySlug(params.slug);
  const uncheckedPost = { ...data, slug: params.slug };
  const checkedPost = validatePost(uncheckedPost);
  const { apiKey, formId } = validateEnvParameters();

  const mdxContent = await serialize(content, {
    scope: data as Record<string, unknown>,
    mdxOptions: {
      rehypePlugins: [imageMetadata],
    },
  });

  return {
    props: {
      apiKey,
      content: mdxContent,
      formId,
      post: checkedPost,
    },
  };
};

imageMetadata – https://github.com/Beraliv/beraliv.dev/blob/main/packages/blog2/src/plugins/imageMetadata.ts#L126 – is the loader for Cloudinary images (it uses the path from here – https://github.com/Beraliv/beraliv.dev/blob/main/packages/blog2/src/functions/imageLoader/index.ts#L12)

How to reproduce

Example of the blog post – Advanced Get – https://blog2.beraliv.dev/2021-03-26-typed-get

The MD file is available here – https://github.com/Beraliv/beraliv.dev/blob/main/packages/blog2/src/content/2021-03-26-typed-get.md

Check it locally

  1. You need to checkout to test/debug-hyperlink-issues branch (PR – https://github.com/Beraliv/beraliv.dev/pull/217)
  2. You need to prepare it:
git clone https://github.com/Beraliv/beraliv.dev.git
cd beraliv.dev
git checkout test/debug-hyperlink-issues
yarn dev
  1. Then open http://localhost:3000/2021-03-26-typed-get
  2. And check Summary ⚓️ link (it should be /2021-03-26-typed-get/#summary but instead you will see https://github.com/type-challenges/type-challenges)

Additional question

Can you suggest me how to fix that without adding custom MdxLink components and use it in *.md files?

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Reactions: 1
  • Comments: 19 (1 by maintainers)

Most upvoted comments

@Beraliv @jescalan OMG, I’ve been facing this issue for a while now and your suggestion to use remark-unwrap-images worked for me. Thanks a ton to both of you! I really appreciate Hashicorp’s OSS work, keep it up! 👍

@jescalan thank you again for the help!

@imjuangarcia ah im so glad! 👬

@Beraliv will shoot you an email and we can set up a time to work on this

Yes, a minimal reproduction means that you spin up a new empty nextjs project and include only the minimum amount of code required to reproduce the issue. The steps you mentioned above are pulling down a full project that includes lots of other code for other purposes that may or may not be involved in the problem. When debugging, the first step is narrowing down what is causing the issue, I’m asking you to do some of the narrowing down for me in advance!

Closing the loop here, we got this resolved live by passing unwrapImages to the mdxOptions.remarkPlugins option in mdx remote. Whoo! 🎉

Yeah I think you will need something slightly custom here as noted in the previous comment, unfortunately.

At some point we are going to run into this same issue internally but its not acute for us yet, so it’s difficult for me to prioritize. Are you open to taking a stab at this? I’d be happy to pair or provide guidance

Hi @Beraliv just a heads up that next v12 has changed the next/image wrapper to be a span instead of a div, so that might address this issue!

https://nextjs.org/blog/next-12#breaking-changes

Hey @Beraliv - sorry for the delay on my end but I had a chance to look at this repro this morning and I think I know what the issue is. If you open the console you can see a number of html violations generated by your custom image component. These are causing hydration to behave incorrectly, and must be corrected in order for the page to work. You can remove the image from the markdown content and see that the link resolves properly.

It seems like the root of the problem is the fact that the image is wrapped by a paragraph when it shouldn’t be - you’d need to modify the way remark parses to remove the paragraph wrapper from around the image I think to fix it. You could try this plugin – but you might need a custom one since you’re replacing the image with a react component and that replacement process I think happens before the remark parse.

Sorry I know this isn’t an easy fix, but hopefully this is enough information to get you there!

@jescalan thank you for your patience!

I finally got to creating the demo where I can reproduce the problem: https://github.com/Beraliv/nextjs-with-mdx#how-to-reproduce

Please let me know if I do something wrong