Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
169 changes: 169 additions & 0 deletions apps/website/docs/learn/02-environment-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ slug: /learn/setup

<p className="text-xl">Learn how to configure the environment needed to use React Strict DOM.</p>

* [Expo framework](#expo-framework) - Configure React Strict DOM in an Expo app.
* [Next.js framework](#nextjs-framework) - Configure React Strict DOM in a Next.js website using App Router.

## Expo framework

[Expo](https://expo.dev/) is a production-grade, cross-platform React framework that is the recommended solution for creating apps with React Strict DOM. The instructions in the rest of this guide are tailored to Expo, but can be adapted by readers to work with other frameworks.
Expand Down Expand Up @@ -147,8 +150,174 @@ export function App() {
}
```

## Next.js framework

[Next.js](https://nextjs.org) is a production-grade, web oriented React framework that is the recommended solution that takes full advantage of React’s architecture to enable full-stack React app. Fully compatible with React Strict DOM. The instructions in the rest of this guide are tailored to Next.js, but can be adapted by readers to work with other frameworks.

Follow the Next.js instructions on how to [create a new project](https://nextjs.org/docs/app/getting-started/installation). Then follow the steps in the [Installation](/learn/installation) guide to install React Strict DOM.

## Babel configuration

When using Next.js App Router, babel is not the default compiler, but can still be used.
Create or modify a `babelLoader.config.js` file as follows (note that it's not a `babel.config.js`). This is used to optimize builds and enables static extraction of CSS for web. Learn how to configure the [babel-preset](/api/babel-preset/) in the API docs.

```js title="babelLoader.config.js"
import path from "path";

const config = {
presets: [
[
"next/dist/compiled/babel/preset-typescript",

@javascripter javascripter Jul 31, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

      "next/dist/compiled/babel/preset-typescript",

This line might not be necessary. We started using Turbopack for development at our company(not Meta) and so far we are using the below config without that line.

function getReactStrictDOMBabelLoader() {
  const dev = process.env.NODE_ENV !== 'production'

  return {
    loader: 'babel-loader',
    options: {
      cacheDirectory: true,
      parserOpts: {
        plugins: ['typescript', 'jsx'],
      },
      presets: [
        [
          'react-strict-dom/babel-preset',
          {
            debug: dev,
            dev,
            rootDir: process.cwd(),
          },
        ],
      ],
    },
  }
}

function withReactStrictDOM(nextConfig: NextConfig) {
  return {
    ...nextConfig,
    webpack(config: any, context: WebpackConfigContext) {
      // Configure StyleX / React Strict DOM
      config.module.rules.push({
        test: /\.(js|jsx|ts|tsx)$/,
        exclude: /node_modules(?!\/react-strict-dom)/,
        use: [getReactStrictDOMBabelLoader()],
      })

      if (typeof nextConfig.webpack === 'function') {
        return nextConfig.webpack(config, context)
      }

      return config
    },
    turbopack: {
      ...nextConfig.turbopack,
      rules: {
        ...nextConfig.turbopack?.rules,
        // Note: Intentionally excluding top-level files in the src directory to skip middleware.ts from being processed
        // due to a Turbopack bug (https://github.com/vercel/next.js/issues/79592)
        './src/*/**/*.{js,jsx,ts,tsx}': {
          default: {
            foreign: false,
            loaders: [getReactStrictDOMBabelLoader()],
          },
        },
        '**/node_modules/react-strict-dom/**/*.{js,jsx,ts,tsx}': {
          default: {
            foreign: false,
            loaders: [getReactStrictDOMBabelLoader()],
          },
        },
      },
    },
  }
}

Note that there is currently a bug in Next.js that prevents Turbpack loader from running when middleware.ts exists.
We are working around this limitation by excluding top-level src/*.{ts,tsx} files from being compiled with babel-loader, but this is specific to our setup and probably isn't something we want to recommend here.

foreign: true

is meant to exclude node_modules etc but I'm not too sure if this config is correct as it's not documented well. Just something we added to make it work.

{
allowNamespaces: true,
},
],
[
"react-strict-dom/babel-preset",
{
debug: true,
dev: process.env.NODE_ENV === "development",
rootDir: process.cwd(),
// if you want to use @/* from tsconfig
// a patch is required in node_modules/react-strict-dom/babel/preset.js
// see https://github.com/facebook/react-strict-dom/pull/294 for more information
// aliases: {
// "@/*": [path.join(process.cwd(), "*")],
// },
},
],
],
};

export default config;

```

## PostCSS configuration

[PostCSS](https://postcss.org/) is a tool for generating CSS. It's enabled by default in Next.js and it's the recommended way to extract React Strict DOM styles to static CSS for web builds. `react-strict-dom/postcss-plugin` can be used to extract styles. Create a `postcss.config.mjs` file as follows.

```js title="postcss.config.mjs"
// note that you need to import manually the babel loader so configuration is shared between next.js & postcss compilation
import babelLoader from "./babelLoader.config.js";

const config = {
plugins: {
"react-strict-dom/postcss-plugin": {
include: [
// Include source files to watch for style changes
'src/**/*.{js,jsx,mjs,ts,tsx}',
// List any installed node_modules that include UI built with React Strict DOM
'node_modules/<package-name>/*.js'
],
babelConfig: babelLoader,
useLayers: true,
},
autoprefixer: {},
},
};

export default config;
```

## Next.js configuration

Create or edit the `next.config.js` file as follows. Note that below you will find config for both turbopack or webpack.

```js title="next.config.js"
import type { NextConfig } from "next";

import babelLoader from "./babelLoader.config.js";

function getBabelLoader() {
return {
loader: "babel-loader",
options: babelLoader,
};
}

const nextConfig: NextConfig = {
transpilePackages: ["react-strict-dom"],

turbopack: {
rules: {
"*.{js,jsx,ts,tsx}": {
loaders: [getBabelLoader()],
},
},
},

webpack: (config, { webpack }) => {
config.resolve.mainFields = ["module", "main"];

config.module.rules.push({

@javascripter javascripter Jul 31, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add exclude option in the module rule so that unrelated node_modules are not processed by this babel loader.

        exclude: /node_modules(?!\/react-strict-dom)/,

See:
https://github.com/facebook/stylex/blob/4a4fe98486cf99a9783e10ec07a41452790a17a6/examples/example-nextjs/next.config.js#L21

test: /\.(js|jsx|ts|tsx)$/,
use: [getBabelLoader()],
});

return config;
},
};

export default nextConfig;
```

## App files

Your app needs to include a CSS file that contains a `@stylex` directive. This acts as a placeholder that is replaced by the generated CSS during builds.

```css title="stylex.css"
/* This directive is used by the react-strict-dom postcss plugin. */
/* It is automatically replaced with generated CSS during builds. */
@stylex;
```

Next, import the CSS file in your `layout.tsx`

```js title="src/app/layout.tsx"
// Required for CSS to work on Next.js
import './stylex.css';

export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}
```

## Platform-specific files

Expo supports [platform-specific extensions](https://docs.expo.dev/router/advanced/platform-specific-modules/#platform-specific-extensions) by default. This allows you to create platform-specific implementations of components, hooks, etc.

Other frameworks will require bundler configuration when building for each platform, so as to resolve files based on their file extensions. For example, web bundles should package `*.web.js` file extensions but not `*.native.js` files. These specific file name suffixes are recommended conventions already used by the React Native ecosystem (see the Expo docs above.)

For example, Next.js can easily handle platform-specific extensions with a change in `next.config.js` with the following additions:

```js title="next.config.js"
//...

const nextConfig: NextConfig = {
// ...

turbopack: {
// ...

resolveExtensions: [".web.tsx", ".web.ts", ".web.jsx", ".web.js", ".tsx", ".ts", ".jsx", ".js", ".mjs", ".json"],
},

webpack: (config, { webpack }) => {
// ...

config.resolve.extensions = [ ".web.js", ".web.jsx", ".web.ts", ".web.tsx", ...config.resolve.extensions];
return config;
},
};

export default nextConfig;
```