monorepo-setup

Configure a scalable monorepo with Turborepo and pnpm workspaces.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "monorepo-setup" with this command: npx skills add monkey1sai/openai-cli/monkey1sai-openai-cli-monorepo-setup

Monorepo Setup

Configure a scalable monorepo with Turborepo and pnpm workspaces.

Core Workflow

  • Initialize structure: Create workspace layout

  • Configure pnpm: Setup workspaces

  • Add Turborepo: Configure build pipeline

  • Create shared packages: Common utilities

  • Setup apps: Applications consuming packages

  • Configure CI/CD: Optimized builds

Directory Structure

monorepo/ ├── apps/ │ ├── web/ # Next.js app │ ├── api/ # Express/Fastify API │ └── mobile/ # React Native app ├── packages/ │ ├── ui/ # Shared UI components │ ├── config/ # Shared configs │ ├── tsconfig/ # TypeScript configs │ ├── eslint-config/ # ESLint configs │ └── utils/ # Shared utilities ├── tooling/ │ ├── scripts/ # Build scripts │ └── docker/ # Docker configs ├── turbo.json ├── pnpm-workspace.yaml ├── package.json └── .npmrc

Root Configuration

pnpm Workspace

pnpm-workspace.yaml

packages:

  • "apps/*"
  • "packages/*"
  • "tooling/*"

Root Package.json

{ "name": "monorepo", "private": true, "packageManager": "pnpm@8.15.0", "engines": { "node": ">=20.0.0", "pnpm": ">=8.0.0" }, "scripts": { "build": "turbo run build", "dev": "turbo run dev", "lint": "turbo run lint", "test": "turbo run test", "typecheck": "turbo run typecheck", "clean": "turbo run clean && rm -rf node_modules", "format": "prettier --write "**/.{ts,tsx,md,json}"", "changeset": "changeset", "version-packages": "changeset version", "release": "turbo run build --filter=./packages/ && changeset publish" }, "devDependencies": { "@changesets/cli": "^2.27.0", "prettier": "^3.2.0", "turbo": "^2.0.0", "typescript": "^5.3.0" } }

NPM Configuration

.npmrc

auto-install-peers=true strict-peer-dependencies=false shamefully-hoist=true node-linker=hoisted

Private registry (optional)

@company:registry=https://npm.company.com/

//npm.company.com/:_authToken=${NPM_TOKEN}

Turborepo Configuration

// turbo.json { "$schema": "https://turbo.build/schema.json", "globalDependencies": ["/.env.*local"], "globalEnv": ["NODE_ENV", "CI"], "pipeline": { "build": { "dependsOn": ["^build"], "outputs": ["dist/", ".next/", "!.next/cache/"], "env": ["DATABASE_URL", "API_URL"] }, "dev": { "dependsOn": ["^build"], "cache": false, "persistent": true }, "lint": { "dependsOn": ["^build"], "outputs": [] }, "typecheck": { "dependsOn": ["^build"], "outputs": [] }, "test": { "dependsOn": ["build"], "outputs": ["coverage/**"] }, "clean": { "cache": false } } }

Shared TypeScript Config

// packages/tsconfig/base.json { "$schema": "https://json.schemastore.org/tsconfig", "display": "Default", "compilerOptions": { "composite": false, "declaration": true, "declarationMap": true, "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "inlineSources": false, "isolatedModules": true, "moduleResolution": "bundler", "noUnusedLocals": false, "noUnusedParameters": false, "preserveWatchOutput": true, "skipLibCheck": true, "strict": true, "strictNullChecks": true }, "exclude": ["node_modules"] }

// packages/tsconfig/nextjs.json { "$schema": "https://json.schemastore.org/tsconfig", "display": "Next.js", "extends": "./base.json", "compilerOptions": { "lib": ["dom", "dom.iterable", "ES2022"], "module": "ESNext", "target": "ES2022", "jsx": "preserve", "noEmit": true, "plugins": [{ "name": "next" }], "allowJs": true, "incremental": true, "resolveJsonModule": true } }

// packages/tsconfig/library.json { "$schema": "https://json.schemastore.org/tsconfig", "display": "Library", "extends": "./base.json", "compilerOptions": { "lib": ["ES2022"], "module": "ESNext", "target": "ES2022", "outDir": "dist", "rootDir": "src" } }

Shared UI Package

// packages/ui/package.json { "name": "@repo/ui", "version": "0.0.0", "private": true, "exports": { ".": "./src/index.ts", "./button": "./src/button.tsx", "./card": "./src/card.tsx", "./styles.css": "./src/styles.css" }, "scripts": { "build": "tsup", "dev": "tsup --watch", "lint": "eslint src/", "typecheck": "tsc --noEmit", "clean": "rm -rf dist" }, "peerDependencies": { "react": "^18.0.0", "react-dom": "^18.0.0" }, "devDependencies": { "@repo/eslint-config": "workspace:", "@repo/tsconfig": "workspace:", "@types/react": "^18.2.0", "react": "^18.2.0", "tsup": "^8.0.0", "typescript": "^5.3.0" } }

// packages/ui/src/button.tsx import { ButtonHTMLAttributes, forwardRef } from 'react';

export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> { variant?: 'primary' | 'secondary' | 'outline'; size?: 'sm' | 'md' | 'lg'; }

export const Button = forwardRef<HTMLButtonElement, ButtonProps>( ({ variant = 'primary', size = 'md', className = '', ...props }, ref) => { const baseStyles = 'inline-flex items-center justify-center rounded-md font-medium transition-colors';

const variants = {
  primary: 'bg-blue-600 text-white hover:bg-blue-700',
  secondary: 'bg-gray-200 text-gray-900 hover:bg-gray-300',
  outline: 'border border-gray-300 bg-transparent hover:bg-gray-100',
};

const sizes = {
  sm: 'h-8 px-3 text-sm',
  md: 'h-10 px-4',
  lg: 'h-12 px-6 text-lg',
};

return (
  &#x3C;button
    ref={ref}
    className={`${baseStyles} ${variants[variant]} ${sizes[size]} ${className}`}
    {...props}
  />
);

} );

Button.displayName = 'Button';

// packages/ui/src/index.ts export { Button, type ButtonProps } from './button'; export { Card, type CardProps } from './card';

// packages/ui/tsup.config.ts import { defineConfig } from 'tsup';

export default defineConfig({ entry: ['src/index.ts'], format: ['esm', 'cjs'], dts: true, splitting: false, sourcemap: true, clean: true, external: ['react', 'react-dom'], });

Shared Utils Package

// packages/utils/package.json { "name": "@repo/utils", "version": "0.0.0", "private": true, "main": "./dist/index.js", "module": "./dist/index.mjs", "types": "./dist/index.d.ts", "exports": { ".": { "import": "./dist/index.mjs", "require": "./dist/index.js", "types": "./dist/index.d.ts" } }, "scripts": { "build": "tsup", "dev": "tsup --watch", "test": "vitest run", "test:watch": "vitest", "typecheck": "tsc --noEmit", "clean": "rm -rf dist" }, "devDependencies": { "@repo/tsconfig": "workspace:*", "tsup": "^8.0.0", "typescript": "^5.3.0", "vitest": "^1.2.0" } }

// packages/utils/src/index.ts export { cn } from './cn'; export { formatDate, formatRelativeTime } from './date'; export { debounce, throttle } from './timing'; export { sleep, retry } from './async';

// packages/utils/src/cn.ts import { type ClassValue, clsx } from 'clsx'; import { twMerge } from 'tailwind-merge';

export function cn(...inputs: ClassValue[]): string { return twMerge(clsx(inputs)); }

Next.js App Configuration

// apps/web/package.json { "name": "@repo/web", "version": "0.1.0", "private": true, "scripts": { "dev": "next dev", "build": "next build", "start": "next start", "lint": "next lint", "typecheck": "tsc --noEmit", "clean": "rm -rf .next .turbo" }, "dependencies": { "@repo/ui": "workspace:", "@repo/utils": "workspace:", "next": "^14.1.0", "react": "^18.2.0", "react-dom": "^18.2.0" }, "devDependencies": { "@repo/eslint-config": "workspace:", "@repo/tsconfig": "workspace:", "@types/node": "^20.11.0", "@types/react": "^18.2.0", "@types/react-dom": "^18.2.0", "typescript": "^5.3.0" } }

// apps/web/tsconfig.json { "extends": "@repo/tsconfig/nextjs.json", "compilerOptions": { "baseUrl": ".", "paths": { "@/": ["./src/"] } }, "include": ["next-env.d.ts", "/*.ts", "/.tsx", ".next/types/**/.ts"], "exclude": ["node_modules"] }

// apps/web/src/app/page.tsx import { Button } from '@repo/ui/button'; import { formatDate } from '@repo/utils';

export default function Home() { return ( <main className="flex min-h-screen flex-col items-center justify-center"> <h1 className="text-4xl font-bold">Monorepo App</h1> <p className="mt-4">Today is {formatDate(new Date())}</p> <Button className="mt-4">Click me</Button> </main> ); }

ESLint Config Package

// packages/eslint-config/package.json { "name": "@repo/eslint-config", "version": "0.0.0", "private": true, "exports": { "./base": "./base.js", "./next": "./next.js", "./react": "./react.js", "./library": "./library.js" }, "devDependencies": { "@typescript-eslint/eslint-plugin": "^7.0.0", "@typescript-eslint/parser": "^7.0.0", "eslint": "^8.56.0", "eslint-config-next": "^14.1.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-react": "^7.33.0", "eslint-plugin-react-hooks": "^4.6.0" } }

// packages/eslint-config/base.js module.exports = { parser: '@typescript-eslint/parser', extends: [ 'eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier', ], plugins: ['@typescript-eslint'], env: { node: true, es2022: true, }, rules: { '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }], '@typescript-eslint/no-explicit-any': 'warn', }, ignorePatterns: ['dist/', 'node_modules/', '.turbo/'], };

// packages/eslint-config/next.js module.exports = { extends: [ './base.js', 'next/core-web-vitals', 'plugin:react-hooks/recommended', ], rules: { 'react/react-in-jsx-scope': 'off', }, };

CI/CD Configuration

.github/workflows/ci.yml

name: CI

on: push: branches: [main] pull_request: branches: [main]

env: TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} TURBO_TEAM: ${{ vars.TURBO_TEAM }}

jobs: build: runs-on: ubuntu-latest

steps:
  - uses: actions/checkout@v4
    with:
      fetch-depth: 2

  - uses: pnpm/action-setup@v3
    with:
      version: 8

  - uses: actions/setup-node@v4
    with:
      node-version: 20
      cache: 'pnpm'

  - name: Install dependencies
    run: pnpm install --frozen-lockfile

  - name: Build
    run: pnpm build

  - name: Lint
    run: pnpm lint

  - name: Type check
    run: pnpm typecheck

  - name: Test
    run: pnpm test

Changeset Configuration

// .changeset/config.json { "$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json", "changelog": "@changesets/cli/changelog", "commit": false, "fixed": [], "linked": [], "access": "restricted", "baseBranch": "main", "updateInternalDependencies": "patch", "ignore": ["@repo/web", "@repo/api"] }

Best Practices

  • Workspace protocol: Use workspace:* for internal deps

  • Shared configs: Centralize TypeScript, ESLint

  • Build caching: Enable Turborepo remote caching

  • Incremental builds: Use dependsOn correctly

  • Package exports: Use proper exports field

  • Version management: Use Changesets

  • Clean scripts: Include clean in each package

  • Consistent naming: Use @repo/ prefix

Output Checklist

Every monorepo should include:

  • pnpm-workspace.yaml configuration

  • turbo.json with proper pipeline

  • Shared TypeScript configs

  • Shared ESLint configs

  • UI component library

  • Utility package

  • Proper package.json exports

  • tsup for library building

  • CI/CD with caching

  • Changeset for versioning

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

responsive-design-system

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

rate-limiting-abuse-protection

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

bruno-collection-generator

No summary provided by upstream source.

Repository SourceNeeds Review