---
title: 'React Monorepo Tutorial'
description: In this tutorial you'll create a frontend-focused workspace with Nx.
---
# Building React Apps in an Nx Monorepo
In this tutorial you'll learn how to use React with Nx in a [monorepo (integrated) setup](/concepts/integrated-vs-package-based#integrated-repos).
What are you going to learn?
- how to create a new React application
- how to run a single task (i.e. serve your app) or run multiple tasks in parallel
- how to leverage code generators to scaffold components
- how to modularize your codebase and impose architectural constraints for better maintainability
{% callout type="info" title="Looking for a React standalone app?" %}
Note, this tutorial sets up a repo with applications and libraries in their own subfolders. If you are looking for a React standalone app setup then check out our [React standalone app tutorial](/getting-started/tutorials/react-standalone-tutorial).
{% /callout %}
## Why Use an Integrated Monorepo?
An integrated monorepo is a repository configured with a set of features that work together toward the goal of allowing developers to focus on building features rather than the configuration, coordination and maintenance of the tooling in the repo.
You'll notice that instead of using npm/yarn/pnpm workspaces, projects within the repository are linked using typescript path aliases that are defined in the `tsconfig.base.json` file. Also, since we're creating projects using Nx plugin generators, all new projects come preconfigured with useful tools like Prettier, ESLint and Jest.
Nx Plugins are optional packages that extend the capabilities of Nx, catering to various specific technologies. For instance, we have plugins tailored to React (e.g., `@nx/react`), Vite (`@nx/vite`), Cypress (`@nx/cypress`), and more. These plugins offer additional features, making your development experience more efficient and enjoyable when working with specific tech stacks.
Features of an integrated monorepo:
- [Install dependencies at the root by default](/concepts/decisions/dependency-management#single-version-policy)
- [Scaffold new code with generators](/features/generate-code)
- [Updates dependencies with automated migrations](/features/automate-updating-dependencies)
Visit our ["Why Nx" page](/getting-started/why-nx) for more details.
## Final Code
Here's the source code of the final result for this tutorial.
{% github-repository url="https://github.com/nrwl/nx-recipes/tree/main/react-monorepo" /%}
## Creating a new React Monorepo
Create a new React monorepo with the following command:
```{% command="npx create-nx-workspace@latest react-monorepo --preset=react-monorepo" path="~" %}
NX Let's create a new workspace [https://nx.dev/getting-started/intro]
✔ Application name · react-store
✔ Which bundler would you like to use? · vite
✔ Test runner to use for end to end (E2E) tests · cypress
✔ Default stylesheet format · css
✔ Do you want Nx Cloud to make your CI fast? · Yes
```
Let's name the initial application `react-store`. In this tutorial we're going to use `vite` as a bundler, `cypress` for e2e tests and `css` for styling. The above command generates the following structure:
```
└─ react-monorepo
├─ ...
├─ apps
│ ├─ react-store
│ │ ├─ public
│ │ │ └─ ...
│ │ ├─ src
│ │ │ ├─ app
│ │ │ │ ├─ app.module.css
│ │ │ │ ├─ app.spec.tsx
│ │ │ │ ├─ app.tsx
│ │ │ │ └─ nx-welcome.tsx
│ │ │ ├─ assets
│ │ │ ├─ main.tsx
│ │ │ └─ styles.css
│ │ ├─ index.html
│ │ ├─ project.json
│ │ ├─ tsconfig.app.json
│ │ ├─ tsconfig.json
│ │ ├─ tsconfig.spec.json
│ │ └─ vite.config.ts
│ └─ react-store-e2e
│ └─ ...
├─ nx.json
├─ tsconfig.base.json
└─ package.json
```
The setup includes..
- a new React application (`apps/react-store/`)
- a Playwright based set of e2e tests (`apps/react-store-e2e/`)
- Prettier preconfigured
- ESLint preconfigured
- Jest preconfigured
Typically, an integrated Nx workspace places application projects in the `apps` folder and library projects in the `libs` folder. Applications are encouraged to be as light-weight as possible so that more code is pushed into libraries and can be reused in other projects. This folder structure is just a suggestion and can be modified to suit your organization's needs.
The [`nx.json` file](/reference/nx-json) contains configuration settings for Nx itself and global default settings that individual projects inherit. The `apps/react-store/project.json` file contains [settings that are specific to the `react-store` project](/reference/project-configuration). We'll examine that file more in the next section.
## Serving the App
To serve your new React application, just run:
```shell
nx serve react-store
```
Your application should be served at [http://localhost:4200](http://localhost:4200).
Nx uses the following syntax to run tasks:

### Inferred Tasks
Nx identifies available tasks for your project from [tooling configuration files](/concepts/inferred-tasks), `package.json` scripts and the targets defined in `project.json`. To view the tasks that Nx has detected, look in the [Nx Console](/getting-started/editor-setup) project detail view or run:
```shell
nx show project react-store
```
{% project-details title="Project Details View (Simplified)" height="100px" %}
```json
{
"project": {
"name": "react-store",
"type": "app",
"data": {
"root": "apps/react-store",
"targets": {
"build": {
"options": {
"cwd": "apps/react-store",
"command": "vite build"
},
"cache": true,
"dependsOn": ["^build"],
"inputs": [
"production",
"^production",
{
"externalDependencies": ["vite"]
}
],
"outputs": ["{workspaceRoot}/dist/apps/react-store"],
"executor": "nx:run-commands",
"configurations": {}
}
},
"name": "react-store",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "apps/react-store/src",
"projectType": "application",
"tags": [],
"implicitDependencies": []
}
},
"sourceMap": {
"root": ["apps/react-store/project.json", "nx/core/project-json"],
"targets": ["apps/react-store/project.json", "nx/core/project-json"],
"targets.build": ["apps/react-store/vite.config.ts", "@nx/vite/plugin"],
"targets.build.command": [
"apps/react-store/vite.config.ts",
"@nx/vite/plugin"
],
"targets.build.options": [
"apps/react-store/vite.config.ts",
"@nx/vite/plugin"
],
"targets.build.cache": [
"apps/react-store/vite.config.ts",
"@nx/vite/plugin"
],
"targets.build.dependsOn": [
"apps/react-store/vite.config.ts",
"@nx/vite/plugin"
],
"targets.build.inputs": [
"apps/react-store/vite.config.ts",
"@nx/vite/plugin"
],
"targets.build.outputs": [
"apps/react-store/vite.config.ts",
"@nx/vite/plugin"
],
"targets.build.options.cwd": [
"apps/react-store/vite.config.ts",
"@nx/vite/plugin"
],
"name": ["apps/react-store/project.json", "nx/core/project-json"],
"$schema": ["apps/react-store/project.json", "nx/core/project-json"],
"sourceRoot": ["apps/react-store/project.json", "nx/core/project-json"],
"projectType": ["apps/react-store/project.json", "nx/core/project-json"],
"tags": ["apps/react-store/project.json", "nx/core/project-json"]
}
}
```
{% /project-details %}
If you expand the `build` task, you can see that it was created by the `@nx/vite` plugin by analyzing your `vite.config.ts` file. Notice the outputs are defined as `{workspaceRoot}/dist/apps/react-store`. This value is being read from the `build.outDir` defined in your `vite.config.ts` file. Let's change that value in your `vite.config.ts` file:
```ts {% fileName="apps/react-store/vite.config.ts" %}
export default defineConfig({
// ...
build: {
outDir: './build/react-store',
// ...
},
});
```
Now if you look at the project details view, the outputs for the build target will say `{workspaceRoot}/build/react-store`. This feature ensures that Nx will always cache the correct files.
You can also override the settings for inferred tasks by modifying the [`targetDefaults` in `nx.json`](/reference/nx-json#target-defaults) or setting a value in your [`project.json` file](/reference/project-configuration). Nx will merge the values from the inferred tasks with the values you define in `targetDefaults` and in your specific project's configuration.
## Adding Another Application
Nx plugins usually provide [generators](/features/generate-code) that allow you to easily scaffold code, configuration or entire projects. To see what capabilities the `@nx/react` plugin provides, run the following command and inspect the output:
```{% command="npx nx list @nx/react" path="react-monorepo" %}
NX Capabilities in @nx/react:
GENERATORS
init : Initialize the `@nrwl/react` plugin.
application : Create a React application.
library : Create a React library.
component : Create a React component.
redux : Create a Redux slice for a project.
storybook-configuration : Set up storybook for a React app or library.
component-story : Generate storybook story for a React component
stories : Create stories/specs for all components declared in an app or library.
component-cypress-spec : Create a Cypress spec for a UI component that has a story.
hook : Create a hook.
cypress-component-configuration : Setup Cypress component testing for a React project
component-test : Generate a Cypress component test for a React component
setup-tailwind : Set up Tailwind configuration for a project.
setup-ssr : Set up SSR configuration for a project.
host : Generate a host react application
remote : Generate a remote react application
federate-module : Federate a module.
EXECUTORS/BUILDERS
module-federation-dev-server : Serve a host or remote application.
module-federation-ssr-dev-server : Serve a host application along with it's known remotes.
```
{% callout type="info" title="Prefer a more visual UI?" %}
If you prefer a more integrated experience, you can install the "Nx Console" extension for your code editor. It has support for VSCode, IntelliJ and ships a LSP for Vim. Nx Console provides autocompletion support in Nx configuration files and has UIs for browsing and running generators.
More info can be found in [the integrate with editors article](/getting-started/editor-setup).
{% /callout %}
Run the following command to generate a new `inventory` application. Note how we append `--dry-run` to first check the output.
```{% command="npx nx g @nx/react:app inventory --directory=apps/inventory --dry-run" path="react-monorepo" %}
NX Generating @nx/react:application
✔ Would you like to add React Router to this application? (y/N) · false
✔ Which E2E test runner would you like to use? · cypress
✔ What should be the project name and where should it be generated? · inventory @ apps/inventory
CREATE apps/inventory/index.html
CREATE apps/inventory/public/favicon.ico
CREATE apps/inventory/src/app/app.spec.tsx
CREATE apps/inventory/src/assets/.gitkeep
CREATE apps/inventory/src/main.tsx
CREATE apps/inventory/tsconfig.app.json
CREATE apps/inventory/src/app/nx-welcome.tsx
CREATE apps/inventory/src/app/app.module.css
CREATE apps/inventory/src/app/app.tsx
CREATE apps/inventory/src/styles.css
CREATE apps/inventory/tsconfig.json
CREATE apps/inventory/project.json
CREATE apps/inventory/tsconfig.spec.json
CREATE apps/inventory/vite.config.ts
CREATE apps/inventory/.eslintrc.json
CREATE apps/inventory-e2e/project.json
CREATE apps/inventory-e2e/src/e2e/app.cy.ts
CREATE apps/inventory-e2e/src/support/app.po.ts
CREATE apps/inventory-e2e/src/support/e2e.ts
CREATE apps/inventory-e2e/src/fixtures/example.json
CREATE apps/inventory-e2e/src/support/commands.ts
CREATE apps/inventory-e2e/cypress.config.ts
CREATE apps/inventory-e2e/tsconfig.json
CREATE apps/inventory-e2e/.eslintrc.json
NOTE: The "dryRun" flag means no changes were made.
```
As you can see, it generates a new application in the `apps/inventory/` folder. Let's actually run the generator by removing the `--dry-run` flag.
```shell
npx nx g @nx/react:app inventory --directory=apps/inventory
```
## Sharing Code with Local Libraries
When you develop your React application, usually all your logic sits in the `app` folder. Ideally separated by various folder names which represent your "domains". As your app grows, however, the app becomes more and more monolithic and the code is unable to be shared with other applications.
```
└─ react-monorepo
├─ ...
├─ apps
│ └─ react-store
│ ├─ ...
│ ├─ src
│ │ ├─ app
│ │ │ ├─ products
│ │ │ ├─ cart
│ │ │ ├─ ui
│ │ │ ├─ ...
│ │ │ └─ app.tsx
│ │ ├─ ...
│ │ └─ main.tsx
│ ├─ ...
│ └─ project.json
├─ nx.json
├─ ...
```
Nx allows you to separate this logic into "local libraries". The main benefits include
- better separation of concerns
- better reusability
- more explicit "APIs" between your "domain areas"
- better scalability in CI by enabling independent test/lint/build commands for each library
- better scalability in your teams by allowing different teams to work on separate libraries
### Creating Local Libraries
Let's assume our domain areas include `products`, `orders` and some more generic design system components, called `ui`. We can generate a new library for each of these areas using the React library generator:
```
nx g @nx/react:library products --directory=libs/products --unitTestRunner=vitest --bundler=none
nx g @nx/react:library orders --directory=libs/orders --unitTestRunner=vitest --bundler=none
nx g @nx/react:library shared-ui --directory=libs/shared/ui --unitTestRunner=vitest --bundler=none
```
Note how we type out the full path in the `directory` flag to place the libraries into a subfolder. You can choose whatever folder structure you like to organize your projects. If you change your mind later, you can run the [move generator](/nx-api/workspace/generators/move) to move a project to a different folder.
Running the above commands should lead to the following directory structure:
```
└─ react-monorepo
├─ ...
├─ apps
├─ libs
│ ├─ products
│ │ ├─ ...
│ │ ├─ project.json
│ │ ├─ src
│ │ │ ├─ index.ts
│ │ │ └─ lib
│ │ │ ├─ products.spec.ts
│ │ │ └─ products.ts
│ │ ├─ tsconfig.json
│ │ ├─ tsconfig.lib.json
│ │ ├─ tsconfig.spec.json
│ │ └─ vite.config.ts
│ ├─ orders
│ │ ├─ ...
│ │ ├─ project.json
│ │ ├─ src
│ │ │ ├─ index.ts
│ │ │ └─ ...
│ │ └─ ...
│ └─ shared
│ └─ ui
│ ├─ ...
│ ├─ project.json
│ ├─ src
│ │ ├─ index.ts
│ │ └─ ...
│ └─ ...
├─ ...
```
Each of these libraries
- has a project details view where you can see the available tasks (e.g. running tests for just orders: `nx test orders`)
- has its own `project.json` file where you can customize targets
- has the name you specified in the generate command; you can find the name in the corresponding `project.json` file
- has a dedicated `index.ts` file which is the "public API" of the library
- is mapped in the `tsconfig.base.json` at the root of the workspace
### Importing Libraries into the React Applications
All libraries that we generate automatically have aliases created in the root-level `tsconfig.base.json`.
```json {% fileName="tsconfig.base.json" %}
{
"compilerOptions": {
...
"paths": {
"@react-monorepo/products": ["libs/products/src/index.ts"],
"@react-monorepo/orders": ["libs/orders/src/index.ts"],
"@react-monorepo/shared-ui": ["libs/shared/ui/src/index.ts"]
},
...
},
}
```
Hence we can easily import them into other libraries and our Angular application. As an example, let's use the pre-generated `ProductsComponent` component from our `libs/products` library.
You can see that the `Products` component is exported via the `index.ts` file of our `products` library so that other projects in the repository can use it. This is our public API with the rest of the workspace. Only export what's really necessary to be usable outside the library itself.
```ts {% fileName="libs/products/src/index.ts" %}
export * from './lib/products';
```
We're ready to import it into our main application now. First (if you haven't already), let's set up React Router.
{% tabs %}
{% tab label="npm" %}
```shell
npm add react-router-dom
```
{% /tab %}
{% tab label="yarn" %}
```shell
yarn add react-router-dom
```
{% /tab %}
{% tab label="pnpm" %}
```shell
pnpm add react-router-dom
```
{% /tab %}
{% /tabs %}
Configure it in the `main.tsx`.
```tsx {% fileName="apps/react-store/src/main.tsx" %}
import { StrictMode } from 'react';
import { BrowserRouter } from 'react-router-dom';
import ReactDOM from 'react-dom/client';
import App from './app/app';
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
This is a change. 👋
This is a change. 👋