Native monorepo management with npm workspaces and on-demand package execution with npx. Node.js v22.x, npm v10.x (as of 2026-02-10).
Core Setup: npm Workspaces
Initialize a monorepo and define workspace roots in the root package.json to enable npm’s native monorepo capabilities.
// root/package.json
{
"name": "my-monorepo",
"version": "1.0.0",
"private": true, // Prevents accidental publishing of the root package
"workspaces": [ // Defines directories containing workspace packages
"packages/*", // Example: packages/ui-lib, packages/utils
"apps/*" // Example: apps/web, apps/admin
],
"scripts": {
"build": "npm run build --workspaces", // Runs 'build' script in all workspaces
"test": "npm test --workspaces" // Runs 'test' script in all workspaces
},
"devDependencies": {
"typescript": "^5.3.3" // Common dev dependencies are often hoisted to the root
}
}
Each workspace package also has its own package.json.
// packages/ui-lib/package.json
{
"name": "@my-monorepo/ui-lib", // Scoped package name for internal usage
"version": "1.0.0",
"main": "dist/index.js", // Entry point after build
"scripts": {
"build": "tsc" // Example build script for TypeScript
},
"dependencies": {
"react": "^18.2.0" // UI library specific dependencies
}
}
Local Package Linking & Consumption
An application within the monorepo consumes a shared UI library from another workspace using the workspace: protocol.
// apps/web/package.json
{
"name": "@my-monorepo/web",
"version": "1.0.0",
"private": true, // Often true for applications not meant for publishing
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build"
},
"dependencies": {
"@my-monorepo/ui-lib": "workspace:*", // Links to the local ui-lib package
"react": "^18.2.0",
"react-dom": "^18.2.0"
}
}
The application can then import components directly from the linked workspace package.
// apps/web/src/App.js
import React from 'react';
import { Button } from '@my-monorepo/ui-lib'; // Import directly from the workspace package
function App() {
return (
<div>
<h1>My Web App</h1>
<Button onClick={() => alert('Clicked!')}>Click Me</Button>
</div>
);
}
export default App;
Dependency Hoisting & Resolution
npm install at the monorepo root intelligently manages dependencies, hoisting common ones to the root node_modules to optimize disk space and installation time.
# Execute from the monorepo root directory
npm install
# npm analyzes all workspace package.json files.
# Dependencies with compatible versions (e.g., 'react') are hoisted
# to the root 'node_modules' to avoid duplication.
# Unique or conflicting versions remain in their respective workspace's 'node_modules'.
# 'workspace:*' dependencies are automatically symlinked by npm,
# pointing to the actual local package directory.
Shared UI Library Development
Developing a reusable React component library and making it available to other applications within the monorepo.
// packages/ui-lib/src/Button.js
import React from 'react';
const Button = ({ children, onClick }) => { // Simple functional component
return (
<button
style={{ padding: '10px 20px', backgroundColor: '#007bff', color: 'white', border: 'none', borderRadius: '5px', cursor: 'pointer' }}
onClick={onClick}
>
{children}
</button>
);
};
export default Button;
// packages/ui-lib/src/index.js
export { default as Button } from './Button'; // Export components for consumption
Gotchas & Best Practices: Build Order
Shared packages must be built before any dependent applications. This ensures that the consuming application finds the compiled output when building itself.
// root/package.json (extended scripts for build orchestration)
{
"name": "my-monorepo",
"version": "1.0.0",
"private": true,
"workspaces": [
"packages/*",
"apps/*"
],
"scripts": {
"build:ui-lib": "npm run build --workspace=@my-monorepo/ui-lib", // Build specific workspace
"build:web": "npm run build --workspace=@my-monorepo/web",
"build:all": "npm run build:ui-lib && npm run build:web", // Enforce correct build order
"start:web": "npm start --workspace=@my-monorepo/web",
"lint": "npm run lint --workspaces" // Run linting across all workspaces
},
"devDependencies": {
"lerna": "^8.0.0", // Optional: Lerna can assist with versioning and publishing workflows
"typescript": "^5.3.3"
}
}
- Versioning: Use
workspace:*for local dependencies to automatically link to the current version within the monorepo. For external packages, use exact versions or caret/tilde ranges. private: true: Setprivate: trueinpackage.jsonfor packages not intended for public npm registry.
Advanced Pattern: npx for Temporary Tools
npx (Node Package eXecutor) runs npm package binaries without explicit installation, ideal for one-off commands or specific tool versions.
# Execute a package binary without installing it globally or locally
npx create-react-app my-new-app --template typescript
# npx temporarily downloads 'create-react-app' and its dependencies,
# executes the binary, then removes them.
# Run a specific version of a tool
npx eslint@8.56.0 . --fix
# Ensures a particular ESLint version is used, regardless of local install.
# Execute a binary from a local 'node_modules/.bin' (similar to npm run)
# If 'eslint' is a devDependency, 'npm run eslint' is common.
# 'npx eslint' also works and will find the local binary first.
npx eslint .
# Prioritizes local installation, then temporary download if not found.
# Security note: Always verify packages before executing with npx,
# especially from unknown sources, as it runs arbitrary code.
Advanced Pattern: CI/CD with GitHub Actions
Configuring GitHub Actions for monorepos with workspaces requires specific caching and explicit build steps.
# .github/workflows/ci.yml
name: Monorepo CI
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4 # Use the latest stable checkout action
- name: Setup Node.js
uses: actions/setup-node@v4 # Use Node.js v22.x for 2026
with:
node-version: '22'
cache: 'npm' # Enable npm cache
cache-dependency-path: '**/package-lock.json' # Cache based on lock files
- name: Install dependencies
run: npm install # Installs all dependencies across workspaces, leveraging hoisting
- name: Build UI Library
run: npm run build --workspace=@my-monorepo/ui-lib # Explicitly build shared library first
- name: Build Web Application
run: npm run build --workspace=@my-monorepo/web # Then build the application
- name: Run tests
run: npm test --workspaces # Run tests across all workspaces
actions/checkout@v4: Ensures the repository is available.actions/setup-node@v4: Sets up Node.js.cache: 'npm'andcache-dependency-pathare crucial for performance, cachingnode_modulesbased onpackage-lock.jsonchanges.npm install: Run once at the root to install all workspace dependencies.- Explicit Build Order: Separate steps for building shared libraries and applications ensure correct dependency resolution.
npm test --workspaces: Runs tests in all defined workspaces.
Quick Reference
- npm Workspaces: Native monorepo solution (npm v7+).
private: true: In rootpackage.jsonand unpublishable workspaces.workspacesarray: Defines paths to workspace packages in rootpackage.json.workspace:*: Dependency range for linking local packages.- Hoisting: Common dependencies move to root
node_modules. npm install: Run at root to install all workspace dependencies.npm run <script> --workspace=<name>: Run script in a specific workspace.npm run <script> --workspaces: Run script in all workspaces.- npx: Executes package binaries without persistent installation.
- Use for: One-off commands, specific tool versions, local binary execution.
- Performance: Can be slower due to download, but avoids global pollution.
- Security: Verify packages, as
npxexecutes arbitrary code.
- CI/CD: Cache
node_modules, ensure correct build order (shared libs first). - Common Error: “Cannot find module” if shared library not built before app build.
References
This page is AI-assisted. References official documentation.