A comprehensive ESLint and Prettier configuration for TypeScript projects, with support for monorepos and ESM. Optimized for Next.js and React applications.
🛠 A comprehensive ESLint and Prettier configuration for TypeScript projects, with support for monorepos and ESM. Optimized for Next.js and React applications. ⚡️
react, hooks, next, and moretsconfig.json for consistent TypeScript settingsInitialize your project (if needed):
npm init
Install the ESLint config:
npm i -D @hyperse/eslint-config-hyperse
Create ESLint configuration:
eslint.config.js (or eslint.config.mjs for CommonJS) in your project rootThe shared config targets ES2023 and pins lib explicitly to
["ES2023", "DOM", "DOM.Iterable", "DOM.AsyncIterable", "WebWorker.ImportScripts", "ScriptHost"],
so the built-in type declarations no longer depend on the target default. Projects needing
newer built-ins can opt in without forking the base config:
tsconfig.json)tsconfig.build.json)Add to your package.json:
Or for JavaScript-only projects:
Install the ESLint extension
Configure VS Code settings (.vscode/settings.json):
Use definePrettierConfig() so Prettier CLI matches ESLint prettier/prettier (base options and prettier-plugin-tailwindcss, with the plugin always last when you add more plugins). The name mirrors this package’s defineConfig helper for ESLint.
Create prettier.config.mjs:
Optional slim import (no ESLint stack): @hyperse/eslint-config-hyperse/definePrettierConfig (the legacy path @hyperse/eslint-config-hyperse/getPrettierConfig still resolves to the same module).
The previous name getPrettierConfig remains exported but is deprecated; prefer definePrettierConfig.
Configure Tailwind via definePrettierConfig (paths are resolved relative to this config file):
isolatedModules: true (default)verbatimModuleSyntax: true (default)The following ESLint rules may impact performance during linting/formatting:
prettier/prettier: Can be slow on large files due to full reformattingsonarjs/arguments-order: Complex analysis of function argument usage patternsConsider selectively disabling these rules for specific files if performance becomes an issue.
@typescript-eslint/no-deprecated is opt-inThis rule is the only type-aware rule the preset ships, and it is disabled by
default because enabling it is what pushes eslint . past the default Node
heap on larger repositories.
Measured on a ~2k-file monorepo package spanning 4 TS projects, from repeated runs against the default Node heap (~4GB):
| configuration | outcome |
|---|---|
projectService: true + no-deprecated: warn | eslint . exits with Ineffective mark-compacts near heap limit |
projectService: true + no-deprecated: off | completes; needs >3GB of heap |
TIMING=1 also shows the rule consuming ~27.8s of rule time — 62% of the
total, and ~2.3x the next most expensive rule (prettier/prettier at ~12.1s).
Re-enable it per project when the repository is small enough to afford it:
Keep the
filesselector, and don't enable it with the CLI--ruleflag.--ruleapplies to every file ESLint visits, not just TypeScript. Files matched by other configs —.jsonamong them — carry noparserOptions, so the run aborts with:Error while loading rule '@typescript-eslint/no-deprecated':
projectService stays on, and whyprojectService builds a TypeScript program covering every linted file, and
that program is where the remaining >3GB of heap goes. It is kept enabled
because @typescript-eslint/consistent-type-imports depends on it for
correctness, not just accuracy.
The rule reads emitDecoratorMetadata and experimentalDecorators from parser
services and, when both are set, skips any file containing a decorator
(typescript-eslint#5468).
Imports in those files are still referenced at runtime through design:type
metadata, so rewriting them with import type changes behaviour.
Without the program that guard is silently disabled. On the package above,
turning projectService off made the rule report 615 new errors — e.g. in
a TypeORM entity where the import is used only in a decorated property:
The suggested autofix would turn that into import type, replacing the emitted
metadata with Object. That is why this preset keeps projectService on by
default.
If you want the ~512MB heap instead and do not rely on
consistent-type-imports, turn both off together:
import { base, defineConfig } from '@hyperse/eslint-config-hyperse';
export default defineConfig([
...base,
{
rules: {
'@typescript-eslint/no-explicit-any': 'off',
},
},
]);
Configure it in eslint.config.js as above instead.