Topics

On this page

Last updated on Dec 5, 2025

React skeleton

It may include:

https://github.com/rtCamp/react-skeleton

Monorepo

A monorepo is a single repository containing multiple projects/packages (apps, libraries, tools, configs), but each is modular and independent.

root/
 ├── .github
 ├── .husky
 ├── apps/
 │    ├── docs-design-system/
 │    └── web/
 ├── packages/
 │    ├── design-system/
 │    ├── config-eslint/
 │    └── config-typescript/
 └── package.json
 └── .nvmrc
 └── pnpm-lock.yaml
 └── pnpm-workspace.yaml
 └── README.md

Why is monorepo better than monolith repo?

In general, think of a monorepo as a ‘One big pizza’  : hard to share, everyone must eat the same thing.
On the other hand, a monorepo is ‘A buffet’  : multiple dishes, you pick what you need, but everything is in one place.

Examples of monorepos

Why use pnpm

Featurenpmpnpm
Disk Space UsageInstalls a full copy of every package in each project’s node_modules, leading to duplication.Uses a global content-addressable store with symlinks, so packages are stored once and reused across projects.
Dependency StructureFlat node_modules can cause phantom dependencies (using a package not explicitly installed).Strict node_modules, prevents phantom dependencies and enforces proper installs.
SpeedSlower installs, especially for large projects and monorepos.Much faster installs due to symlinks and global caching.
Workspaces/MonoreposWorkspace support exists, but limited and less mature.Strong, built-in workspace support designed for monorepos.
CI/CD PerformanceHeavier lockfile (package-lock.json) and slower caching.Smaller pnpm-lock.yaml, better caching, supports pnpm fetch for optimized Docker builds.

Pnpm solves Phantom Dependency

A phantom dependency is a package that your project can import and use, even though it is not listed in your project’s package.json.

This happens because of the way npm (and sometimes yarn) flattens the node_modules folder.

Example

{
  "dependencies": {
    "package-a": "1.0.0"
  }
}

Inside package-a’s package.json

{
  "dependencies": {
    "lodash": "4.17.21"
  }
}

What happens with npm

Use of packages

To maintain consistency and reusability across all apps in the monorepo, we have created separate configuration packages under packages/.

These include:

Each package has its own script. When each package does its own work, we can make things dramatically faster,

Benefits of using packages and config packages in a monorepo

  1. Centralized configuration – Instead of duplicating ESLint, Jest, Prettier, Tailwind, and TypeScript configs in every app, you keep them in one place (packages/config-*). Updates are made once and flow to all apps,  reducing drift and inconsistency.
  2. Consistency across projects – All apps (apps/web, apps/api, etc.) use the same coding standards, test setup, and build configurations. Developers don’t need to guess which rules apply where, everything is uniform.
  3. Easier maintenance – One change updates all consumers. Example: upgrading ESLint rules only requires updating config-eslint. Less chance of forgetting to update a config in one app.
  4. Flexibility with overrides – Each app can still override defaults if needed.
  5. Scales well for large teams – Multiple teams working on different apps don’t waste time maintaining separate configs. Reduces merge conflicts in duplicated config files.

Naming conventions

The folder structure and naming conventions in this monorepo follow industry best practices inspired by popular open-source monorepos (e.g., Next.js, Shopify’s monorepos, and others). The goal is to make the repo:

 apps

 packages

docs

.husky

Design tokens

Design tokens define the foundational design values of the system, such as colors, typography, spacing, and breakpoints, and ensure a consistent look and feel across all apps in the monorepo.

Current Setup – Tailwind + CSS

We are using TailwindCSS and defining our design tokens inside a shared stylesheet:

`packages/config-tailwind/shared-styles.css`

@import 'tailwindcss';
@import 'tw-animate-css';

:root {
  font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
  line-height: 1.5;
  font-weight: 400;

  color-scheme: light dark;
  color: rgba(255, 255, 255, 0.87);
  background-color: #242424;

  --radius: 0.625rem;
  --background: oklch(1 0 0);
  --foreground: oklch(0.145 0 0);
  --card: oklch(1 0 0);
  --card-foreground: oklch(0.145 0 0);
}

These tokens are exposed as CSS custom properties (–variables), which are then consumed by Tailwind and the UI layer across apps.

This approach works well with Tailwind because:


Credits

Authored by Sayed Sayed Sayed Taqui Director of Engineering – React , Imran Imran Imran Sayed Senior WordPress Engineer , Ayush Ayush Ayush Nirwal Senior Software Engineer , Amoghavarsha Amoghavarsha Amoghavarsha Kudaligi Senior Software Engineer , Mayank Mayank Mayank Rana Senior Software Engineer