
Research
Node.js Fixes AsyncLocalStorage Crash Bug That Could Take Down Production Servers
Node.js patched a crash bug where AsyncLocalStorage could cause stack overflows to bypass error handlers and terminate production servers.
@bacons/mdx
Advanced tools
Build-time MDX for Expo apps and websites.
yarn add @bacons/mdx
Add support for importing md and mdx files in your metro.config.js file.
metro.config.js
const { getDefaultConfig } = require("expo/metro-config");
const config = getDefaultConfig(__dirname);
config.resolver.sourceExts.push("md", "mdx");
config.transformer.babelTransformerPath = require.resolve("./transformer.js");
module.exports = config;
Create a custom metro transformer. This is used to transform MDX files into JS + React Native before transpiling with Babel.
./transformer.js
const upstreamTransformer = require("@expo/metro-config/babel-transformer");
const MdxTransformer = require("@bacons/mdx/metro-transformer");
module.exports.transform = async (props) => {
// Then pass it to the upstream transformer.
return upstreamTransformer.transform(
// Transpile MDX first.
await MdxTransformer.transform(props)
);
};
Create a markdown file:
./demo.mdx
import { CustomComponent } from './my-custom-component';
# Hello World
I **am** a _markdown_ file!
<CustomComponent />
This file can be imported and treated as a React component:
./App.js
import Demo from "./demo.mdx";
export default function App() {
return <Demo />;
}
By default, this package uses an incomplete set of universal React Native components for DOM elements. You may wish to improve the components, add more, or swap them out for your own.
import { Text } from "react-native";
import { MDXComponents } from "@bacons/mdx";
export default function App() {
return (
<Demo
components={{
h1: (props) => <h1 {...props} />,
// Add custom components which can be used as JSX elements.
RedText: (props) => <Text {...props} style={{ color: "red" }} />,
// This can be used as `<RedText />` without the need to import it.
}}
/>
);
}
Now inside of your markdown file, you can use the custom components:
# Hello World
<RedText />
You can set the components for all children using the MDXComponents React context component.
import { Text } from "react-native";
import { MDXComponents } from "@bacons/mdx";
export default function App() {
// Pass any HTML element as a key to the MDXComponents component.
return (
<MDXComponents
components={{
h1: (props) => <Text {...props} />,
// Add custom components which can be used as JSX elements.
RedText: (props) => <Text {...props} style={{ color: "red" }} />,
// This can be used as `<RedText />` without the need to import it.
}}
>
<Demo />
</MDXComponents>
);
}
Be sure to pass the
styleprop down to the component you're using, this is how the styles are cascaded.
This package works similarly to most docs sites. You create high-level styles for the entire site. This can be cascaded down to reduce the scope of a style.
import { MDXStyles } from "@bacons/mdx";
export default function App() {
// Pass any HTML element as a key to the MDXStyles component.
return (
<MDXStyles
h1={{
fontSize: 32,
fontWeight: "bold",
color: "red",
}}
>
<Demo />
</MDXStyles>
);
}
The <MDXStyles> components can be stacked in different levels, think of these like CSS classes.
You can add support for importing .mdx files in your tsconfig.json file.
tsconfig.json
{
"compilerOptions": {
"typeRoots": ["./global.d.ts"]
},
"extends": "expo/tsconfig.base"
}
Now create a file that declares the module.
./global.d.ts
declare module "*.mdx" {
import React from "react";
import { CustomComponentsProp } from "@bacons/mdx";
const Component: React.FC<{
components?: CustomComponentsProp;
}>;
export default Component;
}
Optional native-only step, not required for MDX to work.
React Native has suboptimal error messages for when you use React DOM components on native or render strings outside of <Text /> elements. This can make migration and code sharing very painful. This package has an experimental dev-only feature to print out optimized errors when you render react-dom built-in's such as div, p, h1, etc. on native.
Simply add the following to your babel.config.js, and clear the transform cache npx expo start --clear:
module.exports = function (api) {
api.cache(true);
return {
presets: [["babel-preset-expo", { jsxImportSource: "@bacons/mdx/jsx" }]],
};
};
Now when you render a div, p, h1, etc. on native, you will get a helpful error message.
export default function App() {
return <div>Hey</div>;
}
ERROR Unsupported DOM <p /> at: /Users/evanbacon/Documents/GitHub/bacons/mdx/apps/demo/src/App.tsx:1:11
This will break in production.
It's possible to parse MDX to DOM elements instead of universal components. This can be useful when building for web-only or migrating from web-only. To do this, pull in the getDOMComponents function and pass it to the components prop of the MDX component.
import { getDOMComponents } from "@bacons/mdx";
import Demo from "./readme.md";
export default function App() {
return <Demo components={getDOMComponents()} />;
}
This will render the following MDX as DOM elements:
# Hello World
I **am** a _markdown_ file!
And the DOM:
<h1>Hello World</h1>
<p>I <strong>am</strong> a <em>markdown</em> file!</p>
transpile-modules within your next.config.js:
'@bacons/mdx',
'@bacons/react-views',
'@expo/html-elements',
This is a universal MDX implementation for Expo (React & Metro). It aims to be a general-purpose MDX implementation for Expo projects that leverage universal Metro (Expo CLI).
apps/demo project.This library powers my (Evan Bacon) personal blog, the source can be found here: Evan Bacon portfolio.
FAQs
Universal MDX renderer for Expo Router
The npm package @bacons/mdx receives a total of 787 weekly downloads. As such, @bacons/mdx popularity was classified as not popular.
We found that @bacons/mdx demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Research
Node.js patched a crash bug where AsyncLocalStorage could cause stack overflows to bypass error handlers and terminate production servers.

Research
/Security News
A malicious Chrome extension steals newly created MEXC API keys, exfiltrates them to Telegram, and enables full account takeover with trading and withdrawal rights.

Security News
CVE disclosures hit a record 48,185 in 2025, driven largely by vulnerabilities in third-party WordPress plugins.