apps/docs/content/docs/guides/tools/eslint.mdx
import { CreateTurboCallout } from "./create-turbo-callout.tsx";
ESLint is a static analysis tool for quickly finding and fixing problems in your JavaScript code.
<CreateTurboCallout />In this guide, we'll cover:
lint task (applies to both versions)We will share configurations across the monorepo's Workspace, ensuring configuration is consistent across packages and composable to maintain high cache hit ratios.
Using ESLint v9's Flat Configs, we will end up with a file structure like this:
<Files> <Folder name="apps" defaultOpen> <Folder name="docs" defaultOpen> <File name="package.json" /> <File name="eslint.config.js" green /> </Folder><Folder name="web" defaultOpen>
<File name="package.json" />
<File name="eslint.config.js" green />
</Folder>
<Folder name="ui" defaultOpen>
<File name="eslint.config.js" green />
<File name="package.json" />
</Folder>
This structure includes:
@repo/eslint-config in ./packages/eslint-config that holds all ESLint configurationeslint.config.jsui package that also has its own eslint.config.jsThe @repo/eslint-config package has three configuration files, base.js, next.js, and react-internal.js. They are exported from package.json so that they can be used by other packages, according to needs. Examples of the configurations can be found in the Turborepo GitHub repository and are available in npx create-turbo@latest.
Notably, the next.js and react-internal.js configurations use the base.js configuration for consistency, extending it with more configuration for their respective requirements. Additionally, notice that the package.json for eslint-config has all of the ESLint dependencies for the repository. This is useful, since it means we don't need to re-specify the dependencies in the packages that import @repo/eslint-config.
In our web app, we first need to add @repo/eslint-config as a dependency.
We can then import the configuration like this:
import { nextJsConfig } from "@repo/eslint-config/next-js";
/** @type {import("eslint").Linter.Config} */
export default nextJsConfig;
Additionally, you can add configuration specific to the package like this:
import { nextJsConfig } from "@repo/eslint-config/next-js";
/** @type {import("eslint").Linter.Config} */
export default [
...nextJsConfig,
// Other configurations
];
Using legacy configuration from ESLint v8 and lower, we will end up with a file structure like this:
<Files> <Folder name="apps" defaultOpen> <Folder name="docs" defaultOpen> <File name="package.json" /> <File name=".eslintrc.js" green /> </Folder><Folder name="web" defaultOpen>
<File name="package.json" />
<File name=".eslintrc.js" green />
</Folder>
<Folder name="ui" defaultOpen>
<File name=".eslintrc.js" green />
<File name="package.json" />
</Folder>
There's a package called @repo/eslint-config, and two applications, each with their own .eslintrc.js.
@repo/eslint-config packageThe @repo/eslint-config file contains two files, next.js, and library.js. These are two different ESLint configurations, which we can use in different packages, depending on our needs.
A configuration for Next.js may look like this:
/* Custom ESLint configuration for use with Next.js apps. */
module.exports = {
extends: [
"eslint-config-turbo",
"eslint-config-next",
// ...your other ESLint configurations
].map(require.resolve),
// ...your other configuration
};
The package.json looks like this:
{
"name": "@repo/eslint-config",
"version": "0.0.0",
"private": true,
"devDependencies": {
"eslint": "^8",
"eslint-config-turbo": "latest",
"eslint-config-next": "latest"
}
}
Note that the ESLint dependencies are all listed here. This is useful, since it means we don't need to re-specify the dependencies inside the apps which import @repo/eslint-config.
@repo/eslint-config packageIn our web app, we first need to add @repo/eslint-config as a dependency.
We can then import the config like this:
module.exports = {
root: true,
extends: ["@repo/eslint-config/next.js"],
};
By adding @repo/eslint-config/next.js to our extends array, we're telling ESLint to look for a package called @repo/eslint-config, and reference the file next.js.
lint taskThe package.json for each package where you'd like to run ESLint should look like this:
{
"scripts": {
"lint": "eslint ."
}
}
With your scripts prepared, you can then create your Turborepo task:
{
"tasks": {
"lint": {
"dependsOn": ["^lint"]
}
}
}
Using dependsOn with ^lint ensures that changes to dependencies like @repo/eslint-config will invalidate the cache for your lint task, even though the configuration package doesn't have a lint script itself.
You can now run turbo lint with global turbo or create a script in your root package.json:
{
"scripts": {
"lint": "turbo run lint"
}
}