docusaurus: TOC does not work when importing one MDX into another

🐛 Bug Report

On our site we have a couple of duplicate doc pages. To avoid repeating content we have one doc as the source of truth then import the content like so:

import Content from './doc1.md';

<Content />

The issue is, the table of contents is not picked up on the pages that import the content. Please let me know if there is a better way to handle duplicate pages and if this usage is just conceptually wrong

Have you read the Contributing Guidelines on issues?

Yes

To Reproduce

npx @docusaurus/init@latest init my-website classic

in doc2:

import Content from './doc1.md';

<Content />

It’s worth noting that this also happens on my 6 month old version, so I don’t think that it’s due to anything recent

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 62
  • Comments: 53 (10 by maintainers)

Commits related to this issue

Most upvoted comments

@Josh-Cena I understand. TOC is curial part of a Documentation website. And MDX is indented to make multiple files. I wonder someone raised this issue long ago, yet there is no progress so far. I’m not experienced enough to contribute right now. But I believe this deserves more priority.

For anyone waiting to fix this bug, I am happy to say that today I fixed this confusing behaviour in Nextra@2.12.0 via the custom remark plugin written by me.

After receiving feedback from my community I want to contribute to Docusaurus too via a generic remark plugin that we could use in both frameworks Docusaurus & Nextra.

Stay tuned!

Here you can see a test with snapshot result

https://github.com/shuding/nextra/blob/main/packages/nextra/__test__/compile.test.ts#L83-L123

update:

also, this will works for deep imports

when foo.md is imported in bar.mdx that imported in index.mdx

https://github.com/shuding/nextra/issues/1540#issuecomment-1690760661

image

This has been fixed (main/canary) as part of https://github.com/facebook/docusaurus/pull/9684, and will be released in Docusaurus v3.2

Thanks for the inspiration @dimaMachina and the initial port @anatolykopyl 🤗

A temporary workaround could be to export your own TOC manually, eventually re-using the TOC exported by the MDX partials you used.

I’ve used this trick on our changelog page, that imports the changelog from the root of the repo: https://github.com/facebook/docusaurus/pull/5331

This can work well particularly if your existing doc is just an “index” for many imported docs and does not declare its own headings:

import Chapter1, {toc as Chapter1TOC} from "_chapter1.md"
import Chapter2, {toc as Chapter2TOC} from "_chapter2.md"

<Chapter1 />

<Chapter2 />

export const toc = [...Chapter1TOC, ...Chapter2TOC];

This becomes less usable once you add headings between the 2 imported chapters

Hi there! I actually worked on this for our site and think I may have a potential solution. View my PR to our public docs site here: https://github.com/dbt-labs/docs.getdbt.com/pull/2395

Is there anyone that would want to pair on this and see if I can contribute this?

@jbltx we only support importing other MDX files atm

However, it could be technically possible to also support React imported components, and try to get the compiler to output something looking like this:

import Partial, 
  {toc as __tocPartial} from "_partial.mdx"
import MyComponent from "MyComponent"

# Doc

## Doc heading

<Partial/>

<MyComponent/>

export const toc = [
  {
    "value": "Doc heading",
    "id": "doc-heading",
    "level": 2
  }, 
  ...__tocPartial,
  ...MyComponent?.toc?.() 
] 

Note that in any case, it would be your responsibility to implement the toc function attribute:

const MyComponent = () => <h2 id="hello">Hello</h2>

MyComponent.toc = () => {
  return [{id: "hello", value: "Hello", level: 2}];
};

export default MyComponent;

We can’t execute the React code and infer the TOC from that execution.

Would that be a helpful addition, or is this too limited?

For anyone waiting to fix this bug, I am happy to say that today I fixed this confusing behaviour in Nextra@2.12.0 via the custom remark plugin written by me.

After receiving feedback from my community I want to contribute to Docusaurus too via a generic remark plugin that we could use in both frameworks Docusaurus & Nextra.

Stay tuned!

Here you can see a test with snapshot result

https://github.com/shuding/nextra/blob/main/packages/nextra/__test__/compile.test.ts#L83-L123

Wonderful. I needed the target file combining the partials to show the right sidebar. Because of this bug, I have decided to take a somewhat circuitous route of adding the ## headers in the target file and then including the partial below it, for several such subsections. Your fix, when it arrives, will be godsend. Thanks!

I submitted a PR for Docusaurus v2, that I’ll be willing to work on, but in the meantime, until it is accepted, I made a quick drop in replacement remark plugin that fixes this behaviour: @akopyl/docusaurus-toc-patcher.

It implements the logic from https://github.com/shuding/nextra/pull/2199.

if that helps I remembered that Gatsby has a way to extract all imports/exports from MDX.

https://github.com/gatsbyjs/gatsby/blob/2e42197025e2e1bac06c721c3cc44135bf8ef526/packages/gatsby-plugin-mdx/utils/gen-mdx.js#L199

Would that be a helpful addition, or is this too limited?

Thank you for the suggestion @slorber, I think your example is enough for my use case indeed! 👍

Nice feature, do you plan to also support headings from custom JSX/TSX components to appear in ToC ? I have a React component that reads a JSON file and create headings and paragraphs based on it (the JSON file is loaded as a module but I guess it means the ToC should be generated dynamically at runtime and not at compile time?)

We’ve done it like this

import Introduction, {
    toc as IntroductionToc,
} from "../shared/introduction.mdx";
import IntroductionContent, {toc as IntroductionContentToc} from "./.introduction_content.mdx";

export const toc = [...IntroductionContentToc, ...IntroductionToc]

<IntroductionContent />

<Introduction />

But then you can’t mix it with other headers, like this

...

export const toc = [...IntroductionContentToc, ...IntroductionToc]

<IntroductionContent />

## Other header <---- WONT WORK PROPERLY

<Introduction />

Any news on this subject?

Seems like we are waiting for @dimaMachina to create a remark plugin with his fix.

Yes! Sorry, currently I am busy with Nextra 3 release and future speak in Amsterdam on 8 November, I hope to do it next month ✌️

@ltribolet If the page you are importing the partial to already has a TOC, then no, you would probably need something discussed in #6201 to do it.

Done, hope to get this soon. @sweeneyskirt-sl 🤞

I have not heard of any updates to this issue yet, @khushal87. I did throw in a feature request at https://docusaurus.canny.io/feature-requests/p/include-transclusion-content-headings-in-toc so please feel free to vote it up so we can get this on the roadmap at least! 😃

Yes @jknoxville this is what I have in mind: a recursive visitor that is able to “flatten” the TOC structure of multiple MDX docs (and preserve the correct TOC ordering).

This code already runs in node (it’s a Webpack loader)