Introduction
Managing multiple packages in a monorepo is powerful, but keeping consistent linting rules across them can quickly get messy. In this guide, you’ll learn how to:
- Set up a monorepo with Turborepo
- Share and enforce consistent ESLint rules across all packages
- Use
pre-commithooks withHuskyandlint-staged - Automate with Turborepo pipelines
Step 1: Set Up the Turborepo Monorepo
npx create-turbo@latest my-turbo-monorepo
cd my-turbo-monorepo
pnpm install
This creates a structure like:
my-turbo-monorepo/
├── apps/
│ └── web/
├── packages/
│ └── ui/
├── turbo.json
├── package.json
Step 2: Create a Shared ESLint Config
Create a package to hold your shared ESLint rules:
mkdir -p packages/eslint-config
cd packages/eslint-config
pnpm init -y
Install dependencies:
pnpm add -D eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-config-prettier eslint-plugin-react
Add the shared config in index.js:
// packages/eslint-config/index.js
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint', 'react'],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react/recommended',
'prettier'
],
rules: {
semi: ['error', 'always'],
quotes: ['error', 'single'],
'no-console': 'warn',
'react/react-in-jsx-scope': 'off',
},
settings: {
react: {
version: 'detect',
},
},
};
Update the package.json of the eslint-config package:
{
"name": "@my-org/eslint-config",
"version": "1.0.0",
"main": "index.js"
}
Step 3: Use the Shared Config in Apps/Packages
Install your shared config in any app/package:
cd apps/web
pnpm add -D @my-org/eslint-config
Then create .eslintrc.js in apps/web:
module.exports = {
extends: ['@my-org/eslint-config'],
};
Step 4: Add ESLint, Prettier, Husky, and lint-staged to Root
Install at root:
pnpm add -D eslint prettier husky lint-staged
Add .prettierrc to enforce formatting:
{
"semi": true,
"singleQuote": true,
"printWidth": 100
}
Add a .lintstagedrc.json:
{
"**/*.{js,ts,tsx}": ["eslint --fix", "prettier --write"]
}
Set up Husky:
npx husky install
pnpm pkg set scripts.prepare="husky install"
npx husky add .husky/pre-commit "npx lint-staged"
Step 5: Setup Stylelint for Consistent Styling
Just like ESLint for JavaScript/TypeScript, Stylelint helps enforce consistent styling in your CSS, SCSS, or PostCSS files across the entire monorepo.
Create a Shared Stylelint Config Package
In your monorepo:
mkdir -p packages/stylelint-config
cd packages/stylelint-config
pnpm init -y
Install dependencies (adjust for your setup):
pnpm add -D stylelint stylelint-config-standard stylelint-config-prettier stylelint-order
Optional (for SCSS or Tailwind projects):
pnpm add -D stylelint-scss stylelint-config-recommended-scss stylelint-config-tailwindcss
Create index.js:
// packages/stylelint-config/index.js
module.exports = {
extends: [
'stylelint-config-standard',
'stylelint-config-prettier',
'stylelint-config-recommended-scss', // optional
'stylelint-config-tailwindcss', // optional
],
plugins: ['stylelint-order'],
rules: {
'color-hex-case': 'lower',
'declaration-empty-line-before': null,
'order/properties-alphabetical-order': true,
},
};
Add to package.json:
{
"name": "@my-org/stylelint-config",
"version": "1.0.0",
"main": "index.js"
}
Use the Shared Stylelint Config
In any app or package (e.g. apps/web):
pnpm add -D stylelint @my-org/stylelint-config
Create .stylelintrc.js:
module.exports = {
extends: ['@my-org/stylelint-config'],
};
Optional .stylelintignore:
node_modules
dist
build
Add to lint-staged for Pre-commit Checks
Update your .lintstagedrc.json:
{
"**/*.{js,ts,tsx}": ["eslint --fix", "prettier --write"],
"**/*.{css,scss}": ["stylelint --fix", "prettier --write"]
}
Step 6: Add Style lint to Turbo Pipeline (optional)
Add Style lint to Turbo Pipeline
In turbo.json:
{
"pipeline": {
"lint:style": {
"outputs": []
}
}
}
In your package scripts:
{
"scripts": {
"lint:style": "stylelint '**/*.{css,scss}'"
}
}
Add Lint to Turbo Pipelines
In turbo.json:
{
"$schema": "https://turborepo.org/schema.json",
"pipeline": {
"lint": {
"outputs": []
},
"build": {
"dependsOn": ["^build"],
"outputs": [".next/**", "dist/**"]
}
}
}
And in package.json:
{
"scripts": {
"lint": "turbo run lint"
}
}
Each app/package should now define its own lint script like:
{
"scripts": {
"lint": "eslint . --ext .ts,.tsx"
}
}
Step 7 : Enforce Commit Message Convention with Commitlint (optional)
Maintaining consistent commit messages improves collaboration, release management, and enables tools like semantic-release.
Install Commitlint and Husky Hook
At the root of your monorepo:
pnpm add -D @commitlint/{cli,config-conventional}
Create a commitlint.config.js at the root:
// commitlint.config.js
module.exports = {
extends: ['@commitlint/config-conventional'],
};
Add Commit-msg Hook with Husky
Make sure Husky is already initialized (.husky/ folder exists).
Add the commit-msg hook:
npx husky add .husky/commit-msg 'npx --no -- commitlint --edit $1'
This hook will run every time a commit is made, and validate the commit message format.
Example Commit Messages (Using Conventional Commits)
feat: add new carousel componentfix: resolve crash on loginchore: update dependenciesrefactor: simplify Redux logicdocs: update READMEtest: add coverage for auth utils
Optional: Create .commitlintrc.json Instead of commitlint.config.js
If you prefer JSON format:
// .commitlintrc.json
{
"extends": ["@commitlint/config-conventional"]
}
Final Monorepo Folder Structure
my-turbo-monorepo/
├── apps/
│ └── web/ # App folder
│ ├── .eslintrc.js # Extends shared ESLint config
│ ├── .stylelintrc.js # Extends shared Stylelint config
│ └── package.json # Contains lint scripts
│
├── packages/
│ ├── ui/ # Reusable UI package
│ │ ├── .eslintrc.js
│ │ └── .stylelintrc.js
│ │
│ ├── eslint-config/ # Shared ESLint config
│ │ ├── index.js
│ │ └── package.json
│ │
│ └── stylelint-config/ # Shared Stylelint config
│ ├── index.js
│ └── package.json
│
├── .husky/ # Git hooks
│ ├── pre-commit # Runs lint-staged
│ └── commit-msg # Runs commitlint
│
├── .prettierrc # Prettier config
├── .lintstagedrc.json # lint-staged config
├── .eslintignore # ESLint ignore patterns
├── .stylelintignore # Stylelint ignore patterns
├── .gitignore
├── commitlint.config.js # Commitlint config
├── turbo.json # Turbo pipeline config
├── package.json # Root dependencies and lint scripts
└── pnpm-workspace.yaml # Defines workspace packages
Example Root package.json Scripts
{
"scripts": {
"prepare": "husky install",
"lint": "turbo run lint",
"lint:style": "turbo run lint:style",
"format": "prettier --write ."
},
"lint-staged": {
"**/*.{js,ts,tsx}": ["eslint --fix", "prettier --write"],
"**/*.{css,scss}": ["stylelint --fix", "prettier --write"]
}
}
Summary
In this guide, you’ll learn how to set up a scalable monorepo using Turborepo with a strong focus on code quality and consistency. The article walks through configuring shared ESLint and Stylelint rules across apps and packages, integrating Prettier for formatting, and enforcing commit message standards using Commitlint and Husky. You’ll also learn how to automate linting with lint-staged and define Turbo pipelines for streamlined developer workflows. By the end, you’ll have a clean, production-ready monorepo setup that promotes maintainability and team efficiency.