next-mdx-remote: Does not yet support Next.js Version 13
I’ve noticed that next-mdx-remote doesn’t work with Next.js 13.
To reproduce the issue, I tried running the official example with-mdx-remote using Next.js 13 and it doesn’t work. Trying to load the example post, I get the following error:

How to reproduce
- Apply this commit to update run the official example
- Run
yarn dev - Go to http://localhost:3000/posts/example-post
In my own project, I’m getting a similar error relating to imports of components, so I assume the issue might be related to changes in Next.js’ bundler or build system.
I don’t get the error if I run the tests in this repo (hashicorp/next-mdx-remote) with Next.js 13. For me that’s another hint that the error might be related to bundling, since in this repo the test doesn’t import next-mdx-remote through node_modules (see https://github.com/hashicorp/next-mdx-remote/blob/f5b0e74529908efd78b981bae7121847ed751b58/__tests__/fixtures/basic/pages/index.jsx#L5-L6)
Compiled Output
Here is the compiled output of node_modules/next-mdx-remote/index.js:
import React, { useState, useEffect, useMemo } from 'react';
import { jsxRuntime } from './jsx-runtime.cjs';
import * as mdx from '@mdx-js/react';
if (typeof window !== 'undefined') {
window.requestIdleCallback =
window.requestIdleCallback ||
function (cb) {
var start = Date.now();
return setTimeout(function () {
cb({
didTimeout: false,
timeRemaining: function () {
return Math.max(0, 50 - (Date.now() - start))
},
});
}, 1)
};
window.cancelIdleCallback =
window.cancelIdleCallback ||
function (id) {
clearTimeout(id);
};
}
/**
* Renders compiled source from next-mdx-remote/serialize.
*/
function MDXRemote({ compiledSource, frontmatter, scope, components = {}, lazy, }) {
const [isReadyToRender, setIsReadyToRender] = useState(!lazy || typeof window === 'undefined');
// if we're on the client side and `lazy` is set to true, we hydrate the
// mdx content inside requestIdleCallback, allowing the page to get to
// interactive quicker, but the mdx content to hydrate slower.
useEffect(() => {
if (lazy) {
const handle = window.requestIdleCallback(() => {
setIsReadyToRender(true);
});
return () => window.cancelIdleCallback(handle);
}
}, []);
const Content = useMemo(() => {
// if we're ready to render, we can assemble the component tree and let React do its thing
// first we set up the scope which has to include the mdx custom
// create element function as well as any components we're using
const fullScope = Object.assign({ opts: { ...mdx, ...jsxRuntime } }, { frontmatter }, scope);
const keys = Object.keys(fullScope);
const values = Object.values(fullScope);
// now we eval the source code using a function constructor
// in order for this to work we need to have React, the mdx createElement,
// and all our components in scope for the function, which is the case here
// we pass the names (via keys) in as the function's args, and execute the
// function with the actual values.
const hydrateFn = Reflect.construct(Function, keys.concat(`${compiledSource}`));
return hydrateFn.apply(hydrateFn, values).default;
}, [scope, compiledSource]);
if (!isReadyToRender) {
// If we're not ready to render, return an empty div to preserve SSR'd markup
return (React.createElement("div", { dangerouslySetInnerHTML: { __html: '' }, suppressHydrationWarning: true }));
}
// wrapping the content with MDXProvider will allow us to customize the standard
// markdown components (such as "h1" or "a") with the "components" object
const content = (React.createElement(mdx.MDXProvider, { components: components },
React.createElement(Content, null)));
// If lazy = true, we need to render a wrapping div to preserve the same markup structure that was SSR'd
return lazy ? React.createElement("div", null, content) : content;
}
export { MDXRemote };
About this issue
- Original URL
- State: closed
- Created 2 years ago
- Comments: 49 (7 by maintainers)
Commits related to this issue
- Temporarily hardcode config `development` value [skip ci] Until `next-mdx-remote` supports Next 13, we need to workaround the `_jsxDEV` error. For more info, @see https://github.com/hashicorp/next-m... — committed to bespoyasov/www by bespoyasov 2 years ago
- Temp workaround for MDX bug see here: https://github.com/hashicorp/next-mdx-remote/issues/307 — committed to pixelbakery/pixelbakery-website by jordanlambrecht 2 years ago
- Downgrade to next-mdx-remote v4.2.0 See https://github.com/hashicorp/next-mdx-remote/issues/307#issuecomment-1376580829 Signed-off-by: Richard Wall <richard.wall@jetstack.io> — committed to wallrj/website by wallrj a year ago
- pnpm remove next-mdx-remote && pnpm add next-mdx-remote see: https://github.com/hashicorp/next-mdx-remote/issues/307 — committed to thinceller/blog.thinceller.net by thinceller a year ago
- pnpm remove next-mdx-remote && pnpm add next-mdx-remote see: https://github.com/hashicorp/next-mdx-remote/issues/307 — committed to thinceller/blog.thinceller.net by thinceller a year ago
There is a better temporary solution without the need to downgrade
@mdx-js/*to 2.1.5. Just adddevelopment: falseto themdxOptionsargument within theserialize()call:In mdx-js/mdx#2045, compiled MDX source will use
jsxDEVfor rendering in development mode, while in production mode it usesjsxandjsxs.jsxDEVshould be imported fromreact/jsx-dev-runtime, but it is not exported bysrc/jsx-runtime.cjs. This causes theTypeError: _jsxDEV is not a functionerror.I am not able to use MDXContent in a clientcomp. I am getting
TypeError: _jsxDEV is not a functionas an error.impl code:
using node 18 w/ next@13.0.6 and next-mdx-remote@^4.2.0
Hey y’all, we’ve now get experimental support for server components, and the
appdirectory. Take a look at the release notes & documentation and give it a try!Thanks for the report. We haven’t had the opportunity to test it out with v13 yet, but we’ll take a look at this soon!
For anyone else who is blocked by this, you can create a wrapper component (outside of the
/appfolder) like so:And then within the
/appfolder, in apage.tsxfile:The “use client” directive (see docs) makes this a client component so that it has access to local state. I haven’t done any extensive testing here, but I believe the tradeoff is that next-mdx-remote will be included in the JS bundle sent to the client and that the work of translating will take place there, even if the app is otherwise statically rendered.
I’m just going to piggyback off of this issue rather than creating a new one (can if the author prefers).
There seems to be a separate issue when server/static rendering with Next 13.
will result in the following error
However, wrapping the component to force it to client render like so
It seems to “solve” the issue.
I’m too tired to look into it anymore tonight but may poke around tomorrow if no one else is looking into it. But it seems like wrapping it like this is needed without some reorganization of the project, so the MDXRemote component gets loaded in with the “use client” directive.
Just a small note here - we are aware on the nextjs team that the app directory doesn’t yet support mdx-remote. We have it on our roadmap to address this, but aren’t quite there yet. We’re still working on fixing critical bugs and stabilizing it right now, as it’s still beta.
I’ll drop updates here when we start working on this!
Can confirm this works.
For the npm users, the key here is “overrides”
It works if you force-downgrade
@mdx-jsto 2.1.5:I opened a PR (#323) to fix this issue. Before it gets merged, you can use the temporary workaround.
Hi y’all,
As discussed above, we should fully support next 13 as well as the
/appdirectory, documented here). I’m going to close this out, if you have specific issues working withnext-mdx-remoteand next 13 going forward, please feel free to open additional issues with reproductions. Thanks for the discussion here!I ran into the same issue and solved it using
{/* @ts-expect-error Server Component */}.It’s documented on the Next 13 beta docs.
To give a current summary. It took me a while, and I was getting the error back after updates etc. This definitly works:
Do not use this 4.2.0 workaround anymore. Otherwise, you get the error back. Credits to @evowizz for the hint.
After updating to the version
4.2.1, make sure to revert the temporary solution shared by @imtsuki. Essentially, you’d end up doing this:I don’t know if this is related but after upgrading to Next 13.1 we started getting this error:
Note: We’re not using the
appdirectory anywhere yet.EDIT:
Here's what the compiled source looks like
Thanks y’all, I’ll take a look at some of the reported issues with 4.2.1. Note that the previous workaround should likely be removed, and you might need to do a full re-install of
next-mdx-remote:(or whatever the equivalent would be for your package manager of choice)
My package.json:
I got this working by using:
Works under
next devandnext startexecution contexts.Using SSR, then it’s not needed as it turned out. But if you use serialize on client side, then the mdxOptions is needed and have to be switched depending on dev and prod mode.
It looks there is an entanglement with next-remote-watch - at least in my settings.
In
package.json:Running
yarn dev(above script) producesTypeError: _jsx is not a function. However, runningyarn next devworks as expected with the latest versions of nextjs and next-mdx-remote.thanks, @mmiszy for your temp solution, as maybe someone does not know about
selective dependency resolutionsand did this workaround, it might produce Certain edge cases that may not work properly since this is a fairly new feature. so please add a TODO: in your code base to upgrade in the future one maintainer adds a hotfix.worth reading
yarn?Here it is: test-app
Thank you!
Hey all,
In addition to the above, I’m seeing the following build errors while trying to
import { serialize } from 'next-mdx-remote/serialize':This only happens when we try and enable the
runtime: 'experimental-edge'Next config property though.Thought we’d keep all the Next 13 issues in the same place if possible so just tacking on here.
@yannickschuchmann not really - just found a versions combination that seems to work for me:
next-mdx-remote@4.2.1andnext-remote-watch@2.0.0Same for me.
next devworks.next-remote-watchproduces_jsx is not a function4.3.02.0.0_next@13.0.2@ramblehead Did you find a current workaround?
Hi, thanks for this release! With typing I got 2 errors while testing: with MDXRemote:
with compileMdx:
Should I create new issue or is it some error on my side?
I downgraded to
and that fixed the issue.
I’m experiencing this same issue, and did some investigating locally. It seems as though the changes made in https://github.com/hashicorp/next-mdx-remote/pull/323 introduced a bug that surfaces when
next-mdx-remoteis used on Next.js sites in development mode, and not in production builds.For a temporary fix, I pinned the version of
next-mdx-remoteI’m using to4.2.0, and dev mode came right back to mr. #323 is a small change, but i don’t know a ton about howreact/jsx-runtimeis meant to work in dev vs prod mode, so I haven’t been able to come up with a PR that will fix this package. Would be happy to help if someone understands the issue better than i do!e: to be clear, I changed my package.json to the following to fix this for now:
"next-mdx-remote": "4.2.0",Not working for us. We’re on
4.2.1And we’re not using neitherdevelopment: falsenor locking the version to"@mdx-js/mdx": "2.1.5"EDIT:
Also noting that our error is sightly different:
instead of
Trying to understand why a development build is trying to use
_jsxinstead of_jsxDEV. This is only happening on development mode. Making a production build seems to be working fine.It works for me, thanks!
I can say that I still get the same error
@j471n sadly, I have no clue. Let’s see if the maintainers reply.
This worked for me (new appDir with SSG), thanks @mikewheaton !
I can’t even get it to work using the workarounds pasted above.
When I try, I get
Error: Objects are not valid as a React child (found: [object Promise]). If you meant to render a collection of children, use an array instead..Does anybody already have a repo they’re willing to share that works (Next 13 + app directory + MDX), even if its using a workaround?