mdx-js / mdx

Markdown for the component era
https://mdxjs.com
MIT License
17.76k stars 1.14k forks source link

nohoist @mdx-js/react with Next.js: Cannot read properties of null (reading 'useContext') #2499

Closed karlhorky closed 5 months ago

karlhorky commented 5 months ago

Initial checklist

Affected packages and versions

@mdx-js/react@3.0.1, next@14.2.4, react@^18, Yarn v1.22.22

Link to runnable example

No response

Steps to reproduce

  1. Create a folder with a package.json using Yarn v1 Workspaces and configuring nohoist as below
  2. Create dirs packages/x and use create-next-app as below
  3. Add @mdx-js/react in packages/x/package.json (no usage necessary in Next.js app files)
  4. Run yarn workspace x build and observe Cannot read properties of null (reading 'useContext') error 💥

Reproduction repository: https://github.com/karlhorky/repro-mdx-react-next-js-nohoist-cannot-read-usecontext-property

Reproduction commands logs:

➜  p mkdir repro-mdx-react-next-js-nohoist-cannot-read-usecontext-property
➜  p cd repro-mdx-react-next-js-nohoist-cannot-read-usecontext-property

➜  repro-mdx-react-next-js-nohoist-cannot-read-usecontext-property echo '{"private":true,"workspaces":{"packages":["packages/*"],"nohoist":["**/@mdx-js/react","**/@mdx-js/react/**/*"]},"packageManager":"yarn@1.22.22"}' > package.json
➜  repro-mdx-react-next-js-nohoist-cannot-read-usecontext-property cat package.json
{
  "private": true,
  "workspaces": {
    "packages": [
      "packages/*"
    ],
    "nohoist": [
      "**/@mdx-js/react",
      "**/@mdx-js/react/**/*"
    ]
  },
  "packageManager": "yarn@1.22.22"
}

➜  repro-mdx-react-next-js-nohoist-cannot-read-usecontext-property mkdir -p packages/x
➜  repro-mdx-react-next-js-nohoist-cannot-read-usecontext-property cd packages/x

➜  x yarn create next-app . --app --no-turbo --no-src-dir --no-eslint --import-alias @/\* --no-tailwind

...

➜  x yarn add @mdx-js/react

...

➜  x cd ../..
➜  repro-mdx-react-next-js-nohoist-cannot-read-usecontext-property yarn install

...

➜  repro-mdx-react-next-js-nohoist-cannot-read-usecontext-property yarn workspace x build
yarn workspace v1.22.22
yarn run v1.22.22
$ next build
  ▲ Next.js 14.2.4

   Creating an optimized production build ...
 ✓ Compiled successfully
 ✓ Linting and checking validity of types    
 ✓ Collecting page data    
   Generating static pages (0/5)  [=   ]TypeError: Cannot read properties of null (reading 'useContext')
    at exports.useContext (/Users/k/p/repro-mdx-react-next-js-nohoist-cannot-read-usecontext-property/packages/x/node_modules/react/cjs/react.production.min.js:24:495)
    at h (/Users/k/p/repro-mdx-react-next-js-nohoist-cannot-read-usecontext-property/packages/x/.next/server/pages/_error.js:1:5995)
    at Wc (/Users/k/p/repro-mdx-react-next-js-nohoist-cannot-read-usecontext-property/node_modules/react-dom/cjs/react-dom-server.browser.production.min.js:68:44)
    at Zc (/Users/k/p/repro-mdx-react-next-js-nohoist-cannot-read-usecontext-property/node_modules/react-dom/cjs/react-dom-server.browser.production.min.js:70:253)
    at Z (/Users/k/p/repro-mdx-react-next-js-nohoist-cannot-read-usecontext-property/node_modules/react-dom/cjs/react-dom-server.browser.production.min.js:76:89)
    at $c (/Users/k/p/repro-mdx-react-next-js-nohoist-cannot-read-usecontext-property/node_modules/react-dom/cjs/react-dom-server.browser.production.min.js:78:98)
    at bd (/Users/k/p/repro-mdx-react-next-js-nohoist-cannot-read-usecontext-property/node_modules/react-dom/cjs/react-dom-server.browser.production.min.js:77:404)
    at Z (/Users/k/p/repro-mdx-react-next-js-nohoist-cannot-read-usecontext-property/node_modules/react-dom/cjs/react-dom-server.browser.production.min.js:76:217)
    at $c (/Users/k/p/repro-mdx-react-next-js-nohoist-cannot-read-usecontext-property/node_modules/react-dom/cjs/react-dom-server.browser.production.min.js:78:98)
    at Zc (/Users/k/p/repro-mdx-react-next-js-nohoist-cannot-read-usecontext-property/node_modules/react-dom/cjs/react-dom-server.browser.production.min.js:71:145)

Error occurred prerendering page "/500". Read more: https://nextjs.org/docs/messages/prerender-error

TypeError: Cannot read properties of null (reading 'useContext')
    at exports.useContext (/Users/k/p/repro-mdx-react-next-js-nohoist-cannot-read-usecontext-property/packages/x/node_modules/react/cjs/react.production.min.js:24:495)
    at h (/Users/k/p/repro-mdx-react-next-js-nohoist-cannot-read-usecontext-property/packages/x/.next/server/pages/_error.js:1:5995)
    at Wc (/Users/k/p/repro-mdx-react-next-js-nohoist-cannot-read-usecontext-property/node_modules/react-dom/cjs/react-dom-server.browser.production.min.js:68:44)
    at Zc (/Users/k/p/repro-mdx-react-next-js-nohoist-cannot-read-usecontext-property/node_modules/react-dom/cjs/react-dom-server.browser.production.min.js:70:253)
    at Z (/Users/k/p/repro-mdx-react-next-js-nohoist-cannot-read-usecontext-property/node_modules/react-dom/cjs/react-dom-server.browser.production.min.js:76:89)
    at $c (/Users/k/p/repro-mdx-react-next-js-nohoist-cannot-read-usecontext-property/node_modules/react-dom/cjs/react-dom-server.browser.production.min.js:78:98)
    at bd (/Users/k/p/repro-mdx-react-next-js-nohoist-cannot-read-usecontext-property/node_modules/react-dom/cjs/react-dom-server.browser.production.min.js:77:404)
    at Z (/Users/k/p/repro-mdx-react-next-js-nohoist-cannot-read-usecontext-property/node_modules/react-dom/cjs/react-dom-server.browser.production.min.js:76:217)
    at $c (/Users/k/p/repro-mdx-react-next-js-nohoist-cannot-read-usecontext-property/node_modules/react-dom/cjs/react-dom-server.browser.production.min.js:78:98)
    at Zc (/Users/k/p/repro-mdx-react-next-js-nohoist-cannot-read-usecontext-property/node_modules/react-dom/cjs/react-dom-server.browser.production.min.js:71:145)
TypeError: Cannot read properties of null (reading 'useContext')
    at exports.useContext (/Users/k/p/repro-mdx-react-next-js-nohoist-cannot-read-usecontext-property/packages/x/node_modules/react/cjs/react.production.min.js:24:495)
    at h (/Users/k/p/repro-mdx-react-next-js-nohoist-cannot-read-usecontext-property/packages/x/.next/server/pages/_error.js:1:5995)
    at Wc (/Users/k/p/repro-mdx-react-next-js-nohoist-cannot-read-usecontext-property/node_modules/react-dom/cjs/react-dom-server.browser.production.min.js:68:44)
    at Zc (/Users/k/p/repro-mdx-react-next-js-nohoist-cannot-read-usecontext-property/node_modules/react-dom/cjs/react-dom-server.browser.production.min.js:70:253)
    at Z (/Users/k/p/repro-mdx-react-next-js-nohoist-cannot-read-usecontext-property/node_modules/react-dom/cjs/react-dom-server.browser.production.min.js:76:89)
    at $c (/Users/k/p/repro-mdx-react-next-js-nohoist-cannot-read-usecontext-property/node_modules/react-dom/cjs/react-dom-server.browser.production.min.js:78:98)
    at bd (/Users/k/p/repro-mdx-react-next-js-nohoist-cannot-read-usecontext-property/node_modules/react-dom/cjs/react-dom-server.browser.production.min.js:77:404)
    at Z (/Users/k/p/repro-mdx-react-next-js-nohoist-cannot-read-usecontext-property/node_modules/react-dom/cjs/react-dom-server.browser.production.min.js:76:217)
    at $c (/Users/k/p/repro-mdx-react-next-js-nohoist-cannot-read-usecontext-property/node_modules/react-dom/cjs/react-dom-server.browser.production.min.js:78:98)
    at Zc (/Users/k/p/repro-mdx-react-next-js-nohoist-cannot-read-usecontext-property/node_modules/react-dom/cjs/react-dom-server.browser.production.min.js:71:145)

Error occurred prerendering page "/404". Read more: https://nextjs.org/docs/messages/prerender-error

TypeError: Cannot read properties of null (reading 'useContext')
    at exports.useContext (/Users/k/p/repro-mdx-react-next-js-nohoist-cannot-read-usecontext-property/packages/x/node_modules/react/cjs/react.production.min.js:24:495)
    at h (/Users/k/p/repro-mdx-react-next-js-nohoist-cannot-read-usecontext-property/packages/x/.next/server/pages/_error.js:1:5995)
    at Wc (/Users/k/p/repro-mdx-react-next-js-nohoist-cannot-read-usecontext-property/node_modules/react-dom/cjs/react-dom-server.browser.production.min.js:68:44)
    at Zc (/Users/k/p/repro-mdx-react-next-js-nohoist-cannot-read-usecontext-property/node_modules/react-dom/cjs/react-dom-server.browser.production.min.js:70:253)
    at Z (/Users/k/p/repro-mdx-react-next-js-nohoist-cannot-read-usecontext-property/node_modules/react-dom/cjs/react-dom-server.browser.production.min.js:76:89)
    at $c (/Users/k/p/repro-mdx-react-next-js-nohoist-cannot-read-usecontext-property/node_modules/react-dom/cjs/react-dom-server.browser.production.min.js:78:98)
    at bd (/Users/k/p/repro-mdx-react-next-js-nohoist-cannot-read-usecontext-property/node_modules/react-dom/cjs/react-dom-server.browser.production.min.js:77:404)
    at Z (/Users/k/p/repro-mdx-react-next-js-nohoist-cannot-read-usecontext-property/node_modules/react-dom/cjs/react-dom-server.browser.production.min.js:76:217)
    at $c (/Users/k/p/repro-mdx-react-next-js-nohoist-cannot-read-usecontext-property/node_modules/react-dom/cjs/react-dom-server.browser.production.min.js:78:98)
    at Zc (/Users/k/p/repro-mdx-react-next-js-nohoist-cannot-read-usecontext-property/node_modules/react-dom/cjs/react-dom-server.browser.production.min.js:71:145)
 ✓ Generating static pages (5/5)

> Export encountered errors on following paths:
        /_error: /404
        /_error: /500
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
error Command failed.
Exit code: 1

Expected behavior

Adding of @mdx-js/react package with nohoist config should not cause Next.js build to fail with Cannot read properties of null (reading 'useContext') error

Actual behavior

Adding of @mdx-js/react package with nohoist config causes Next.js build to fail with Cannot read properties of null (reading 'useContext') error

Runtime

Node v20

Package manager

yarn v1

OS

macOS

Build and bundle tools

Next.js

karlhorky commented 5 months ago

Workaround 1 (remove nohoist config)

Remove the nohoist config in the root package.json Yarn v1 Workspaces config and then run yarn install again:

package.json

{
  "private": true,
  "workspaces": {
    "packages": [
      "packages/*"
    ],
    "nohoist": [
-      "**/@mdx-js/react",
-      "**/@mdx-js/react/**/*"
    ]
  },
  "packageManager": "yarn@1.22.22"
}

Maybe not possible for some projects.

ChristianMurphy commented 5 months ago

Hey @karlhorky! 👋 Taking a step back, mdx is generally designed to work with npm. With other package managers with different resolution strategies like plug and play and non-hoisted, your mileage may vary.

Why are you using a non-hoisted strategy in the first place? My guess is that you have a conflicting version in dependencies, with npm you can find these with npm ls {package name} likely react. I think the yarn equivalent is yarn list --pattern react, I suspect you have duplicate/conflicting versions.

Duplicate copies of react always causes problems, that is not mdx specific.

remcohaszing commented 5 months ago
  1. You shouldn’t use Yarn 1. It’s old, unmaintained, and known to have bugs.
  2. Next.js app router does not support useContext(), therefor it doesn’t support @mdx-js/react. Create a file mdx-components.tsx in your package root instead. The best documentation for that can be found in https://github.com/mdx-js/mdx-analyzer#mdxprovidedcomponents.
  3. The Next.js team just keeps ignoring to enhance the MDX experience for Next.js or dismisses them based on falsehoods. (https://github.com/vercel/next.js/pull/59693, https://github.com/vercel/next.js/pull/62503, https://github.com/vercel/next.js/pull/64769)
karlhorky commented 5 months ago

Thanks for the tips so far!

I'm using nohoist because we have multiple versions of @mdx-js/react and react in the monorepo, which are causing conflicts when the different apps are built.

  1. You shouldn’t use Yarn 1. It’s old, unmaintained, and known to have bugs.

Indeed, I would love to switch off it. This large monorepo is one of our last projects using it. For now, it's just working around the problems :)

2. Next.js app router does not support useContext(), therefor it doesn’t support @mdx-js/react. Create a file mdx-components.tsx in your package root instead. The best documentation for that can be found in mdx-js/mdx-analyzer#mdxprovidedcomponents.

Ah, Next.js App Router does support useContext() (in Client Components). We already have a better version than this working with mdx-bundler.

It's just crashing currently because we're recursively rendering .mdx files which import other .mdx files, which requires @mdx-js/react, and we're using nohoist to try to avoid the conflicts with the other versions in the monorepo.

karlhorky commented 5 months ago

It's indeed probably caused by duplicate react packages in the different node_modules directories.

I'll see if I can find one more workaround and then we can close it as "working as intended".

remcohaszing commented 5 months ago

Could you just pass the components prop instead of using @mdx-js/react?

karlhorky commented 5 months ago

Oh yeah, actually if either of you would know a simpler way of achieving recursive rendering of MDX files (.mdx files which import other .mdx files, which should also have the same components context), then that would be amazing!

Currently the only one we've seen working is mdx-bundler (we need programmatic access - cannot use app/*/*.mdx directly - since we render MDX content from database dynamically based on slug and add other things to the JSX):

For an example of a "simpler" approach, I would ideally like to an approach like this (similar to the rsc-mdx library by @zhangyu1818), to dynamically evaluate() in RSC:

import { evaluate, type EvaluateOptions } from '@mdx-js/mdx'
import * as runtime from 'react/jsx-runtime'
import remarkGfm from 'remark-gfm';

export async function MDX(props: MDXProps) {
  const { source, ...rest } = props
  const { default: MDXContent } = await evaluate(source, {
    useMDXComponents: () => ({ ... }),
    remarkPlugins: [remarkGfm],
    rehypePlugins: [],
    ...rest,
    ...(runtime as Pick<EvaluateOptions, 'Fragment' | 'jsx' | 'jsxs'>),
  })

  return <MDXContent />
}

But it has problems with importing the child .mdx files, if I remember correctly:

  1. not being able to resolve the imported files (eg. child.mdx imported from parent.mdx)
  2. not adding the components in the MDX context to the child imported files
karlhorky commented 5 months ago

Workaround 2 (add nohoist config for react)

To deduplicate the version of the react package (one version in node_modules/react, one version in packages/x/node_modules/react), another way to avoid the conflicts is to configure nohoist also for react

package.json

{
  "private": true,
  "workspaces": {
    "packages": [
      "packages/*"
    ],
    "nohoist": [
      "**/@mdx-js/react",
      "**/@mdx-js/react/**/*",
+     "**/react"
    ]
  },
  "packageManager": "yarn@1.22.22"
}

This will ensure there is only the react in packages/x/node_modules/react and that this is the version being used in the build.

karlhorky commented 5 months ago

Since it appears that duplicate copies of the react package are causing this problem (caused by the hoisting behavior of Yarn v1 nohoist), I will close this as "working as intended" or "wontfix".

Anyone else running into this Cannot read properties of null (reading 'useContext') error can consider if it is a problem caused by duplicate copies of react, especially in multiple node_modules locations, common in monorepos.