In a growing monorepo, maintaining consistent code quality across multiple apps and packages is crucial. One powerful way to do this is by creating a shared ESLint config package. In this post, Iโll walk you through how I set up a reusable ESLint config inside a Turborepo-based monorepo โ perfect for full-stack projects using TypeScript, React, and Node.js.
โ Related: How to Create a TypeScript Config Package in Turborepo
๐งฉ Step-by-Step: Creating the @dtr-cli/eslint-config
Package
1. Create a Package Folder
Inside your packages
folder, create a new directory for the ESLint config package:
mkdir -p packages/eslint-config
cd packages/eslint-config
npm init -y
2. Update the package.json
with a proper name and export map:
{
"name": "@dtr-cli/eslint-config",
"version": "0.0.1",
"description": "Shared Eslint configuration for @dtr-cli",
"license": "ISC",
"author": "Saiful Islam <saiful.islam.rafi.88@gmail.com>",
"exports": {
"./base-eslint-config": "./base-eslint-config.js",
"./backend-eslint-config": "./backend-eslint-config.js",
"./react-eslint-config": "./react-eslint-config.js",
"./frontend-eslint-config": "./frontend-eslint-config.js",
"./extension-eslint-config": "./extension-eslint-config.js",
"./ui-eslint-config": "./ui-eslint-config.js"
},
"files": [
"base-eslint-config.js",
"backend-eslint-config.js",
"react-eslint-config.js",
"frontend-eslint-config.js",
"extension-eslint-config.js",
"ui-eslint-config.js"
],
"devDependencies": {
"@eslint/js": "^9.26.0",
"@tanstack/eslint-plugin-query": "^5.74.7",
"eslint-config-prettier": "^10.1.3",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.20",
"eslint-plugin-turbo": "^2.5.3",
"globals": "^16.1.0",
"typescript-eslint": "^8.32.0"
}
}
3. Create Base ESLint Config
base-eslint-config.js
This config includes basic settings for TypeScript, TurboRepo, and Prettier:
import eslintJs from "@eslint/js";
import eslintConfigPrettier from "eslint-config-prettier";
import turboPlugin from "eslint-plugin-turbo";
import tseslint from "typescript-eslint";
export const baseEslintConfig = [
eslintConfigPrettier,
eslintJs.configs.recommended,
turboPlugin.configs["flat/recommended"],
...tseslint.configs.recommended,
{
languageOptions: {
ecmaVersion: 2020,
},
rules: {
"turbo/no-undeclared-env-vars": "warn",
},
},
];
4. Create targeted ESLint config
๐ง backend-eslint-config.js
import globals from "globals";
import { baseEslintConfig } from "./base-eslint-config";
export const backendEslintConfig = [
...baseEslintConfig,
{
languageOptions: {
globals: globals.node,
},
},
];
โ๏ธ react-eslint-config.js
import reactEslint from "eslint-plugin-react";
import reactHooks from "eslint-plugin-react-hooks";
import reactRefresh from "eslint-plugin-react-refresh";
import globals from "globals";
import { baseEslintConfig } from "./base-eslint-config";
export const reactEslintConfig = [
...baseEslintConfig,
reactEslint.configs.flat.recommended,
reactHooks.configs["recommended-latest"],
reactRefresh.configs.recommended,
{
settings: {
react: { version: "19.1.0" },
},
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
rules: {
"react/react-in-jsx-scope": "off",
"react/prop-types": "off",
"react-refresh/only-export-components": [
"warn",
{ allowConstantExport: true },
],
},
},
];
๐งฉ frontend-eslint-config.js
import { reactEslintConfig } from "./react-eslint-config";
export const frontendEslintConfig = [...reactEslintConfig];
๐งช extension-eslint-config.js
import { reactEslintConfig } from "./react-eslint-config.js";
import pluginQuery from "@tanstack/eslint-plugin-query";
export const extensionEslintConfig = [
...reactEslintConfig,
...pluginQuery.configs["flat/recommended"],
];
๐จ ui-eslint-config.js
import { reactEslintConfig } from "./react-eslint-config.js";
export const uiEslintConfig = [...reactEslintConfig];
Use the ESLint Config Package
In any project inside your monorepo, like apps/backend
, create an eslint.config.mjs
:
import { backendEslintConfig } from "@dtr-cli/eslint-config/backend-eslint-config";
/** @type {import("eslint").Linter.Config} */
export default [
...backendEslintConfig,
{
ignores: ["dist/**"],
},
];
apps/frontend
create an eslint-config.js
import { frontendEslintConfig } from "@dtr-cli/eslint-config/frontend-eslint-config";
/** @type {import("eslint").Linter.Config} */
export default [...frontendEslintConfig];
Final Thoughts
Having a centralized ESLint config package in your Turborepo setup is a massive productivity and consistency boost. Instead of rewriting and maintaining config files in every app, you can rely on one source of truth that scales with your repo.
This setup supports various contexts like backend, React apps, extensions, and UI libraries โ making your developer experience smoother and your codebase cleaner.
Top comments (0)