Strict, opinionated ESLint flat config for TypeScript projects.
Every rule enforces error — no warnings, no compromises.
- ESLint v9+ flat config only — no legacy
.eslintrc - TypeScript-first with
strictTypeChecked+stylisticTypeChecked - Prettier built-in — formatting as lint errors, zero config
- 7 presets — Base, React, Next.js, Node.js, Convex, Turbo, Boundaries
- Composable — spread arrays, override anything
pnpm add -D @vllnt/eslint-config eslint typescript prettier// eslint.config.js
import { nextjs } from '@vllnt/eslint-config/nextjs'
export default [...nextjs]Each preset is an array — spread it into your flat config. All presets include projectService: true for type-aware linting.
| Import | Use case |
|---|---|
@vllnt/eslint-config |
Base — TypeScript strict + Prettier + import sorting |
@vllnt/eslint-config/nextjs |
Next.js apps (includes React + a11y + core web vitals) |
@vllnt/eslint-config/react |
React apps without Next.js |
@vllnt/eslint-config/nodejs |
Node.js backends |
@vllnt/eslint-config/convex |
Convex backends (4 official + 7 custom rules) |
@vllnt/eslint-config/turbo |
Turborepo cache rules (opt-in) |
@vllnt/eslint-config/boundaries |
Architecture boundary enforcement (opt-in) |
Mix and match — presets are arrays:
import { nodejs } from '@vllnt/eslint-config/nodejs'
import { turbo } from '@vllnt/eslint-config/turbo'
export default [...nodejs, ...turbo]Override any rule by appending a config object:
import { nodejs } from '@vllnt/eslint-config/nodejs'
export default [
...nodejs,
{
rules: {
'max-lines-per-function': ['error', { max: 50 }],
},
},
]| Plugin | What it does |
|---|---|
| @eslint/js | ESLint recommended rules |
| typescript-eslint | strictTypeChecked + stylisticTypeChecked |
| eslint-plugin-prettier | Prettier as ESLint errors |
| eslint-config-prettier | Disables conflicting rules |
| eslint-plugin-perfectionist | Import/object/type sorting |
| eslint-plugin-unicorn | Modern JS best practices |
| eslint-plugin-simple-import-sort | Import ordering |
| eslint-plugin-functional | Functional patterns (no loops, readonly) |
| eslint-plugin-write-good-comments | Comment quality |
Plus: strict naming conventions (camelCase, PascalCase types, T-prefixed generics), no enums (union types enforced via AST selector).
| Plugin | What it does |
|---|---|
| eslint-plugin-react | Recommended + jsx-runtime |
| eslint-plugin-react-hooks | Rules of hooks |
| eslint-plugin-jsx-a11y | Accessibility |
| eslint-plugin-css-modules | CSS module validation |
Everything in React, plus:
| Plugin | What it does |
|---|---|
| @next/eslint-plugin-next | Recommended + core web vitals (all error) |
Route handler method restrictions, page/layout max-lines-per-function override.
| Preset | Plugin |
|---|---|
| Turbo | eslint-plugin-turbo cache rules |
| Boundaries | eslint-plugin-boundaries architecture enforcement |
The Convex preset enforces backend best practices with 4 official rules + 7 custom rules bundled as eslint-plugin-convex-rules.
// Standalone
import { convex } from '@vllnt/eslint-config/convex'
export default [...convex]// With base (recommended)
import { base } from '@vllnt/eslint-config'
import { convex } from '@vllnt/eslint-config/convex'
export default [...base, ...convex]| Rule | Catches |
|---|---|
no-old-registered-function-syntax |
Deprecated function syntax |
require-args-validator |
Missing args validator |
explicit-table-ids |
Implicit table ID types |
import-wrong-runtime |
Wrong runtime imports (Node in Convex runtime) |
| Rule | Catches |
|---|---|
standard-filenames |
Factories outside queries.ts, mutations.ts, actions.ts |
namespace-separation |
query() in mutations.ts, mutation() in queries.ts |
snake-case-filenames |
Hyphens in convex/ filenames (must be snake_case) |
no-bare-v-any |
v.any() outside validators.ts |
require-returns-validator |
Missing returns validator in factory config |
no-query-in-loop |
N+1 queries (ctx.db.query/get/runQuery inside loops) |
no-filter-on-query |
.filter() on query chains (use .withIndex()) |
- Config files (
auth.ts,auth.config.ts,convex.config.ts) — exempt fromsnake-case-filenamesandexplicit-module-boundary-types - Migration files (
convex/migrations/**) — exempt fromstandard-filenames,namespace-separation,no-query-in-loop - Generated/test files (
convex/_generated/**,*.test.ts,convex/testing/**) — excluded entirely
import { base } from '@vllnt/eslint-config'
import { convex } from '@vllnt/eslint-config/convex'
export default [
...base,
...convex,
// Exempt "use node" action files from import-wrong-runtime
{
files: ['convex/agents/actions.ts'],
rules: { '@convex-dev/import-wrong-runtime': 'off' },
},
]convex/
{domain}/
queries.ts query(), internalQuery()
mutations.ts mutation(), internalMutation()
actions.ts action(), internalAction()
internal_mutations.ts internalMutation() (optional split)
validators.ts v.* validators + types
schema.ts table definitions
lib/
validators.ts shared v.any() aliases
_generated/ auto-generated (excluded)
migrations/ relaxed rules
| Package | Required |
|---|---|
eslint >= 9 |
Yes |
prettier >= 3 |
Yes |
typescript >= 5 |
Optional |
Add to .vscode/settings.json for monorepo support:
{
"eslint.workingDirectories": [
"./apps/your-app",
"./packages/your-package"
]
}