Project Scaffolding in 2026: Monorepo, Turborepo and Nx
Structuring new projects in 2026 requires more than `npm init`. Monorepos with Turborepo or Nx offer shared configs, build caching and consistency across multiple packages.
Executive summary
Structuring new projects in 2026 requires more than `npm init`. Monorepos with Turborepo or Nx offer shared configs, build caching and consistency across multiple packages.
Last updated: 3/12/2026
Introduction: The evolution of scaffolding
In 2026, npm init, create-react-app and vue-cli are no longer sufficient for professional projects. Teams require:
- Monorepo for shared configs (ESLint, Prettier, TypeScript)
- Build caching for fast development (rebuilds in seconds, not minutes)
- Shared packages for reuse (UI components, utilities, types)
- Intelligent CI/CD (only modified packages are tested/deployed)
- Scalability (adding new packages should be trivial)
Monorepo tools like Turborepo and Nx have emerged as patterns for modern project scaffolding. They provide structure, convention and automation that transform project setup from repetitive task to efficient, consistent process.
The problem with traditional scaffolding
Fragmented approach
bash# Traditional project setup with multiple packages
# 1. Frontend
$ npx create-react-app frontend
# 2. Backend
$ npx express-generator backend
# 3. Shared
$ mkdir shared && cd shared
$ npm init -y
$ npm install typescript @types/node --save-dev
# 4. Manual config duplication
# Copia ESLint, Prettier, tsconfig between all packages
# 5. Manual dependency management
# npm install in each package, inconsistent versions
# 6. Manual build scripts
# npm run build in each package, without parallelizationProblems:
- Config drift: ESLint, TypeScript configs diverge between packages
- Inconsistent dependencies: React 18 in one package, 17 in another
- Slow builds: No caching, everything is rebuilt every time
- Manual overhead: Adding new package is complex manual process
- Inefficient CI: All packages tested even without changes
Impact on development
┌─────────────────────────────────────────────────────────────────────┐
│ TRADITIONAL SETUP │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Developer: "I want to add a new shared component" │
│ │
│ 1. Create folder in shared/ │
│ 2. Configure tsconfig, Jest, ESLint manually │
│ 3. Add package.json with build scripts │
│ 4. Update frontend/package.json with path │
│ 5. Update backend/package.json with path │
│ 6. Run npm install in each package │
│ 7. Run build manually to verify │
│ │
│ Time: 2-3 hours of manual work │
│ Error chance: High (forgetting to update some package.json) │
│ │
└─────────────────────────────────────────────────────────────────────┘Turborepo: Simplicity with performance
Core concepts
Turborepo is a monorepo tool focused on performance and simplicity:
┌─────────────────────────────────────────────────────────────────────┐
│ TURBOREPO │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌──────────────┐ │
│ │ packages/ │ │ turbo.json │ │
│ │ │ │ │ │
│ │ - web/ │───────│ pipeline: │ │
│ │ - api/ │ │ web#build │ │
│ │ - shared/ │ │ dependsOn: │ │
│ │ - ui/ │ │ ["shared#build"] │ │
│ └─────────────┘ └──────────────┘ │
│ │ │
│ │ │
│ │ Turbo executes only changes │
│ │ with distributed cache │
│ ▼ │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ .turbo/cache/ │ │
│ │ (Local cache + optional remote) │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘Initial setup
bash# Create monorepo with Turborepo
$ npx create-turbo@latest my-monorepo
# Generated structure
my-monorepo/
├── apps/
│ ├── web/ # Frontend app
│ └── docs/ # Docs app
├── packages/
│ ├── ui/ # Shared components
│ └── config/ # Shared configs
├── turbo.json # Turborepo config
└── package.json # Root package.json with workspacesjson// turbo.json
{
"$schema": "https://turbo.build/schema.json",
"globalDependencies": ["**/.env.*local"],
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": [".next/**", "!.next/cache/**", "dist/**"]
},
"test": {
"dependsOn": ["build"],
"outputs": ["coverage/**"]
},
"lint": {
"outputs": []
},
"dev": {
"cache": false,
"persistent": true
}
}
}json// package.json (root)
{
"name": "my-monorepo",
"private": true,
"workspaces": [
"apps/*",
"packages/*"
],
"scripts": {
"build": "turbo run build",
"dev": "turbo run dev",
"test": "turbo run test",
"lint": "turbo run lint"
},
"devDependencies": {
"turbo": "^2.0.0",
"eslint": "^8.0.0",
"prettier": "^3.0.0",
"typescript": "^5.0.0"
}
}Usage patterns
bash# Develop all apps and packages
$ npm run dev
# Build only changed packages
$ npm run build
# Build specific package and dependencies
$ turbo run build --filter=web
# Test only packages changed since last commit
$ turbo run test --filter...[HEAD~1]
# Clear local cache
$ turbo pruneCI/CD integration
yaml# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: 8
- name: Get pnpm store directory
id: pnpm-cache
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
- name: Setup pnpm cache
uses: actions/cache@v3
with:
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Install dependencies
run: pnpm install
- name: Build affected apps
run: pnpm turbo run build --filter=...[origin/main]
- name: Test affected apps
run: pnpm turbo run test --filter=...[origin/main]Nx: Complete monorepo ecosystem
Core concepts
Nx offers more advanced features than Turborepo, with enterprise focus:
┌─────────────────────────────────────────────────────────────────────┐
│ NX │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌──────────────┐ │
│ │ apps/ │ │ nx.json │ │
│ │ - web/ │───────│ generators: │ │
│ │ - api/ │ │ workspace: │ │
│ └─────────────┘ │ lib: │ │
│ │ │ @nx/react │ │
│ │ └──────────────┘ │
│ ┌─────────────┐ ┌──────────────┐ │
│ │ libs/ │ │ affected: │ │
│ │ - ui/ │ │ deps: │ │
│ │ - utils/ │───────│ apps │ │
│ └─────────────┘ └──────────────┘ │
│ │ │
│ │ Nx calculates dependency graph │
│ │ and executes in parallel with cache │
│ ▼ │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ nx/cache (optional remote) │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘Initial setup
bash# Create monorepo with Nx
$ npx create-nx-workspace@latest my-nx-workspace
# Choose: Integrated Monorepo
# Choose: Stack (React, Next.js, NestJS, etc.)
# Choose: CI provider (GitHub Actions, GitLab, etc.)json// nx.json
{
"$schema": "./node_modules/nx/schemas/nx-schema.json",
"namedInputs": {
"default": ["{projectRoot}/**/*", "sharedGlobals"],
"production": [
"default",
"!{projectRoot}/**/?(*.spec|*.test).ts",
"!{projectRoot}/tsconfig.spec.json",
"!{projectRoot}/jest.config.ts",
"!{projectRoot}/src/test-setup.{ts,js}"
],
"sharedGlobals": []
},
"targetDefaults": {
"build": {
"cache": true,
"dependsOn": ["^build"],
"inputs": ["production", "^production"]
},
"test": {
"cache": true,
"inputs": ["default", "^production", "{workspaceRoot}/jest.preset.js"]
},
"lint": {
"cache": true,
"inputs": ["default", "{workspaceRoot}/.eslintrc.json"]
}
},
"plugins": [
"@nx/react",
"@nx/next",
"@nx/jest",
"@nx/eslint"
]
}Generators for automatic scaffolding
Nx provides generators to create apps, libs and components automatically:
bash# Create new Next.js app
$ npx nx g @nx/next:app my-app --directory=apps/my-app
# Create new shared lib
$ npx nx g @nx/react:lib my-lib --directory=libs/my-lib --importPath=@my-monorepo/my-lib
# Create component in lib
$ npx nx g @nx/react:component Button --project=my-lib --export
# Create new NestJS service
$ npx nx g @nx/nest:service users --project=apiResult:
- Configs (tsconfig, Jest, ESLint) created automatically
- Dependencies added to correct package.json
- Imports configured with tsconfig paths
- Build/test scripts updated
Dependency graph visualizer
bash# Visualize project dependency graph
$ npx nx graph
# Opens Nx visual UI
$ npx nx show-project web --web┌─────────────────────────────────────────────────────────────────────┐
│ NX DEPENDENCY GRAPH │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ │
│ │ web │ │
│ └─────┬────┘ │
│ │ │
│ ┌─────────┼─────────┐ │
│ ▼ ▼ ▼ │
│ ┌────────┐ ┌──────┐ ┌────────┐ │
│ │ ui │ │utils │ │config │ │
│ └────────┘ └──────┘ └────────┘ │
│ │ │
│ └─────────────┐ │
│ ▼ │
│ ┌──────────┐ │
│ │ api │ │
│ └──────────┘ │
│ │
│ Web depends on ui, utils, config │
│ API depends on utils, config │
│ Utils depends on config │
│ │
└─────────────────────────────────────────────────────────────────────┘pnpm Workspaces: Dependency management
Workspaces configuration
json// package.json (root)
{
"name": "my-monorepo",
"private": true,
"scripts": {
"dev": "turbo run dev",
"build": "turbo run build"
},
"devDependencies": {
"turbo": "^2.0.0",
"typescript": "^5.0.0"
}
}yaml# pnpm-workspace.yaml
packages:
- 'apps/*'
- 'packages/*'bash# Install dependencies in all packages
$ pnpm install
# Add specific dependency
$ pnpm add axios -w # Add in root workspace
$ pnpm add axios -F web # Add in workspace web
# Link workspaces locally (no npm link)
$ pnpm install # Creates symlinks between workspacesWorkspace scripts
json// apps/web/package.json
{
"name": "web",
"scripts": {
"dev": "next dev",
"build": "next build",
"test": "jest"
},
"dependencies": {
"@my-monorepo/ui": "workspace:*", // Local workspace
"@my-monorepo/utils": "workspace:*",
"next": "^14.0.0",
"react": "^18.0.0"
}
}Advanced monorepo patterns
1. Shared configs (ESLint, Prettier, TypeScript)
typescript// packages/config/eslint/index.ts
import { config } from '@my-monorepo/tsconfig';
export default [
{
ignores: ['node_modules', 'dist', '.next'],
extends: ['next/core-web-vitals', 'prettier'],
rules: {
'@typescript-eslint/no-unused-vars': 'error',
'react-hooks/rules-of-hooks': 'error',
// Shared rules
},
},
// Custom config per project when needed
config.eslintOverrides || {},
];typescript// apps/web/.eslintrc.js
module.exports = require('@my-monorepo/config/eslint');
// Uses shared config, can add project-specific overrides2. Shared types
typescript// packages/types/src/index.ts
export interface User {
id: string;
email: string;
name: string;
}
export interface Product {
id: string;
name: string;
price: number;
}
export type ApiError = {
code: string;
message: string;
details?: unknown;
};typescript// apps/web/src/api/users.ts
import { User, ApiError } from '@my-monorepo/types';
async function getUser(id: string): Promise<User> {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) {
throw await response.json() as ApiError;
}
return response.json();
}3. Shared utilities
typescript// packages/utils/src/date.ts
export function formatDate(date: Date): string {
return new Intl.DateTimeFormat('en-US', {
dateStyle: 'medium',
}).format(date);
}
export function parseDate(dateString: string): Date {
return new Date(dateString);
}
export function addDays(date: Date, days: number): Date {
const result = new Date(date);
result.setDate(result.getDate() + days);
return result;
}typescript// packages/utils/src/api.ts
import axios from 'axios';
export const api = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_URL,
headers: {
'Content-Type': 'application/json',
},
});
export async function fetchWithError<T>(
url: string
): Promise<T> {
try {
const response = await api.get<T>(url);
return response.data;
} catch (error) {
if (axios.isAxiosError(error)) {
throw {
code: error.code,
message: error.message,
} as ApiError;
}
throw error;
}
}Comparing Turborepo vs Nx
When to choose Turborepo
Turbo is ideal when:
- You want simplicity and performance
- Your team already knows standard tools (Next.js, Jest, ESLint)
- You don't need complex generators
- You prefer manual configuration over automation
Advantages:
- Quick, simple setup
- Excellent performance
- Low learning curve
- Natural integration with modern frameworks
When to choose Nx
Nx is ideal when:
- You have large team with multiple squads
- You need generators for consistency
- You want dependency visualization
- You need enterprise integrations (Jira, Azure DevOps)
- You want to automate repetitive patterns
Advantages:
- Powerful generators for scaffolding
- Visual dependency graph
- Enterprise tool integrations
- Greater workflow automation
Migration strategies
Migrating from separate repos to monorepo
bash# 1. Create monorepo
$ npx create-turbo@latest my-monorepo
# 2. Move existing apps
$ mv ../my-app apps/web
$ mv ../my-api apps/api
# 3. Create packages structure
$ mkdir -p packages/ui packages/utils packages/config
# 4. Extract shared code
$ # Move components to packages/ui
$ # Move utils to packages/utils
# 5. Update imports
$ # Swap './components/Button' for '@my-monorepo/ui/Button'
# 6. Configure workspaces
$ # Update package.json with workspaces
$ # Create pnpm-workspace.yaml
# 7. Update CI
$ # Configure turbo for build/test only affectedMigrating from Turborepo to Nx (or vice versa)
The packages/apps structure is compatible between both:
bash# From Turborepo to Nx
$ npx add-nx-to-turbo-workspace
# Converts turbo.json to nx.json
# Adds Nx generators
# Updates scripts
# From Nx to Turborepo
$ npx @nx/workspace-to-turbo
# Converts nx.json to turbo.json
# Removes generators (Turbo doesn't have them)
# Updates scripts60-day implementation plan
Week 1-2: Selection and setup
- Choose between Turborepo and Nx
- Create initial monorepo with scaffolding
- Configure basic CI/CD
Week 3-4: Gradual migration
- Migrate one existing app
- Extract shared code to packages
- Update imports and dependencies
Week 5-6: Optimization and automation
- Configure remote cache (Nx Cloud or Turborepo Remote)
- Create custom generators (if using Nx)
- Document development patterns
- Train team
Success metrics
- Build time: Full build < 2 minutes (with cache)
- Dev startup:
npm run devstarts in < 10 seconds - Cache hit rate: > 80% of builds use cache
- Time to new package: Add new package < 15 minutes
- CI time: CI/CD for PR < 5 minutes (build + test)
Does your team waste time with repetitive setups and slow builds? Talk to Imperialis specialists about modern project scaffolding, from monorepo to CI/CD optimization, to transform setup into productivity.