Strict Configuration

6 min read
Rapid overview

ESLint Strict Configuration

Production-Ready Strict Setup

This guide covers configuring ESLint with strict, best-practice rules for professional TypeScript/React projects.


Complete Strict Configuration

Flat Config (ESLint 9+)

// eslint.config.js
import js from '@eslint/js';
import typescript from 'typescript-eslint';
import react from 'eslint-plugin-react';
import reactHooks from 'eslint-plugin-react-hooks';
import importPlugin from 'eslint-plugin-import';
import prettier from 'eslint-config-prettier';

export default typescript.config(
  // Base recommended configs
  js.configs.recommended,
  ...typescript.configs.strictTypeChecked,
  ...typescript.configs.stylisticTypeChecked,

  // Global ignores
  {
    ignores: ['dist/**', 'node_modules/**', 'coverage/**', '*.config.js'],
  },

  // TypeScript files
  {
    files: ['**/*.ts', '**/*.tsx'],
    languageOptions: {
      parserOptions: {
        project: true,
        tsconfigRootDir: import.meta.dirname,
      },
    },
    plugins: {
      react,
      'react-hooks': reactHooks,
      import: importPlugin,
    },
    settings: {
      react: {
        version: 'detect',
      },
      'import/resolver': {
        typescript: true,
        node: true,
      },
    },
    rules: {
      // === TypeScript Strict Rules ===
      '@typescript-eslint/no-explicit-any': 'error',
      '@typescript-eslint/no-unused-vars': ['error', {
        argsIgnorePattern: '^_',
        varsIgnorePattern: '^_',
      }],
      '@typescript-eslint/explicit-function-return-type': ['error', {
        allowExpressions: true,
        allowTypedFunctionExpressions: true,
      }],
      '@typescript-eslint/explicit-module-boundary-types': 'error',
      '@typescript-eslint/no-non-null-assertion': 'error',
      '@typescript-eslint/strict-boolean-expressions': ['error', {
        allowString: false,
        allowNumber: false,
        allowNullableObject: true,
      }],
      '@typescript-eslint/no-floating-promises': 'error',
      '@typescript-eslint/no-misused-promises': 'error',
      '@typescript-eslint/await-thenable': 'error',
      '@typescript-eslint/no-unnecessary-condition': 'error',
      '@typescript-eslint/prefer-nullish-coalescing': 'error',
      '@typescript-eslint/prefer-optional-chain': 'error',
      '@typescript-eslint/consistent-type-imports': ['error', {
        prefer: 'type-imports',
        fixStyle: 'inline-type-imports',
      }],
      '@typescript-eslint/consistent-type-exports': 'error',
      '@typescript-eslint/no-import-type-side-effects': 'error',
      '@typescript-eslint/naming-convention': [
        'error',
        { selector: 'interface', format: ['PascalCase'] },
        { selector: 'typeAlias', format: ['PascalCase'] },
        { selector: 'enum', format: ['PascalCase'] },
        { selector: 'enumMember', format: ['UPPER_CASE'] },
        { selector: 'variable', format: ['camelCase', 'UPPER_CASE', 'PascalCase'] },
        { selector: 'function', format: ['camelCase', 'PascalCase'] },
        { selector: 'parameter', format: ['camelCase'], leadingUnderscore: 'allow' },
        { selector: 'property', format: ['camelCase', 'UPPER_CASE'] },
        { selector: 'method', format: ['camelCase'] },
      ],

      // === JavaScript Best Practices ===
      'no-console': ['error', { allow: ['warn', 'error'] }],
      'no-debugger': 'error',
      'no-alert': 'error',
      'no-var': 'error',
      'prefer-const': 'error',
      'prefer-arrow-callback': 'error',
      'prefer-template': 'error',
      'no-param-reassign': ['error', { props: true }],
      'no-nested-ternary': 'error',
      'no-unneeded-ternary': 'error',
      'no-else-return': ['error', { allowElseIf: false }],
      'no-return-await': 'error',
      'require-await': 'error',
      'no-await-in-loop': 'warn',
      'no-promise-executor-return': 'error',
      'prefer-promise-reject-errors': 'error',
      'no-throw-literal': 'error',
      'eqeqeq': ['error', 'always'],
      'curly': ['error', 'all'],
      'default-case': 'error',
      'default-case-last': 'error',
      'no-fallthrough': 'error',
      'no-implicit-coercion': 'error',
      'no-magic-numbers': ['warn', {
        ignore: [-1, 0, 1, 2],
        ignoreArrayIndexes: true,
        enforceConst: true,
      }],
      'complexity': ['warn', { max: 10 }],
      'max-depth': ['warn', { max: 3 }],
      'max-lines-per-function': ['warn', { max: 50, skipBlankLines: true, skipComments: true }],
      'max-params': ['warn', { max: 4 }],

      // === React Rules ===
      'react/jsx-uses-react': 'off', // Not needed with React 17+
      'react/react-in-jsx-scope': 'off',
      'react/prop-types': 'off', // Using TypeScript
      'react/jsx-no-target-blank': 'error',
      'react/jsx-no-useless-fragment': 'error',
      'react/jsx-curly-brace-presence': ['error', { props: 'never', children: 'never' }],
      'react/self-closing-comp': 'error',
      'react/jsx-boolean-value': ['error', 'never'],
      'react/jsx-pascal-case': 'error',
      'react/no-array-index-key': 'warn',
      'react/no-danger': 'error',
      'react/no-unstable-nested-components': 'error',
      'react/hook-use-state': 'error',

      // === React Hooks Rules ===
      'react-hooks/rules-of-hooks': 'error',
      'react-hooks/exhaustive-deps': 'error',

      // === Import Rules ===
      'import/order': ['error', {
        groups: [
          'builtin',
          'external',
          'internal',
          ['parent', 'sibling'],
          'index',
          'type',
        ],
        'newlines-between': 'always',
        alphabetize: { order: 'asc', caseInsensitive: true },
      }],
      'import/no-duplicates': 'error',
      'import/no-cycle': 'error',
      'import/no-self-import': 'error',
      'import/no-useless-path-segments': 'error',
      'import/first': 'error',
      'import/newline-after-import': 'error',
      'import/no-default-export': 'warn', // Prefer named exports
    },
  },

  // Test files - relaxed rules
  {
    files: ['**/*.test.ts', '**/*.test.tsx', '**/*.spec.ts', '**/*.spec.tsx'],
    rules: {
      '@typescript-eslint/no-explicit-any': 'off',
      '@typescript-eslint/no-non-null-assertion': 'off',
      'no-magic-numbers': 'off',
      'max-lines-per-function': 'off',
    },
  },

  // Prettier must be last
  prettier,
);

Rule-by-Rule Breakdown

Type Safety Rules

// Disallow any - forces proper typing
'@typescript-eslint/no-explicit-any': 'error'

// Bad
function process(data: any) { }

// Good
function process(data: UserData) { }
// Require explicit return types on exported functions
'@typescript-eslint/explicit-module-boundary-types': 'error'

// Bad
export function getUser(id: string) {
  return db.users.find(id);
}

// Good
export function getUser(id: string): Promise<User | null> {
  return db.users.find(id);
}
// Prevent non-null assertions (!)
'@typescript-eslint/no-non-null-assertion': 'error'

// Bad
const name = user!.name;

// Good
const name = user?.name ?? 'Unknown';
// Strict boolean expressions
'@typescript-eslint/strict-boolean-expressions': 'error'

// Bad - truthy/falsy check
if (value) { }

// Good - explicit check
if (value !== null && value !== undefined) { }
if (value.length > 0) { }
if (value === true) { }

Async/Promise Safety

// Catch floating promises
'@typescript-eslint/no-floating-promises': 'error'

// Bad - unhandled promise
fetchData();

// Good
await fetchData();
// or
void fetchData(); // Explicit ignore
// or
fetchData().catch(handleError);
// Prevent misused promises
'@typescript-eslint/no-misused-promises': 'error'

// Bad - promise in condition
if (fetchData()) { }

// Good
if (await fetchData()) { }

Code Quality Rules

// Complexity limit
'complexity': ['warn', { max: 10 }]

// Function nesting depth
'max-depth': ['warn', { max: 3 }]

// Max function lines
'max-lines-per-function': ['warn', { max: 50 }]

// Max parameters
'max-params': ['warn', { max: 4 }]

// These encourage:
// - Smaller, focused functions
// - Early returns
// - Breaking complex logic into helpers

Import Organization

'import/order': ['error', {
  groups: ['builtin', 'external', 'internal', ['parent', 'sibling'], 'index', 'type'],
  'newlines-between': 'always',
  alphabetize: { order: 'asc' },
}]

// Results in:
import fs from 'fs';                    // builtin

import React from 'react';              // external
import { useQuery } from 'react-query';

import { config } from '@/config';      // internal (aliased)

import { Button } from '../Button';     // parent
import { Input } from './Input';        // sibling

import styles from './styles.css';      // index

import type { User } from '@/types';    // type imports last

Auto-Fix Examples

What eslint --fix Can Fix

// Before
var x = 1;
const y = "hello";
if(x == 1){ console.log(y) }
const fn = function() { return 1; };

// After --fix
const x = 1;
const y = 'hello';
if (x === 1) {
  console.log(y);
}
const fn = () => 1;

Rules with Auto-Fix

RuleFixes
no-varvarlet/const
prefer-constletconst when never reassigned
prefer-arrow-callbackFunction expressions → arrow functions
prefer-templateString concatenation → template literals
eqeqeq=====
quotesQuote style normalization
semiAdd/remove semicolons
import/orderReorder imports
@typescript-eslint/consistent-type-importsAdd type keyword

Rules That Cannot Auto-Fix

  • no-explicit-any - Requires manual typing
  • no-unused-vars - Requires removing or using
  • complexity - Requires refactoring
  • no-floating-promises - Requires await/catch/void

Environment-Specific Configs

Development vs Production

// eslint.config.js
const isProd = process.env.NODE_ENV === 'production';

export default [
  {
    rules: {
      'no-console': isProd ? 'error' : 'warn',
      'no-debugger': isProd ? 'error' : 'warn',
      '@typescript-eslint/no-explicit-any': isProd ? 'error' : 'warn',
    },
  },
];

Framework-Specific: Next.js

import nextPlugin from '@next/eslint-plugin-next';

export default [
  // ... base config
  {
    plugins: {
      '@next/next': nextPlugin,
    },
    rules: {
      ...nextPlugin.configs.recommended.rules,
      ...nextPlugin.configs['core-web-vitals'].rules,
      'import/no-default-export': 'off', // Next.js requires default exports
    },
  },
  {
    files: ['**/page.tsx', '**/layout.tsx', '**/route.ts'],
    rules: {
      'import/no-default-export': 'off',
    },
  },
];

Framework-Specific: Angular

import angular from '@angular-eslint/eslint-plugin';
import angularTemplate from '@angular-eslint/eslint-plugin-template';

export default [
  {
    files: ['**/*.ts'],
    plugins: {
      '@angular-eslint': angular,
    },
    rules: {
      '@angular-eslint/component-class-suffix': 'error',
      '@angular-eslint/directive-class-suffix': 'error',
      '@angular-eslint/no-empty-lifecycle-method': 'error',
      '@angular-eslint/prefer-on-push-component-change-detection': 'warn',
    },
  },
  {
    files: ['**/*.html'],
    plugins: {
      '@angular-eslint/template': angularTemplate,
    },
    rules: {
      '@angular-eslint/template/no-negated-async': 'error',
      '@angular-eslint/template/use-track-by-function': 'warn',
    },
  },
];

CI/CD Integration

GitHub Actions

name: Lint

on: [push, pull_request]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - run: npm ci

      - name: Run ESLint
        run: npm run lint -- --max-warnings 0

      - name: Check types
        run: npm run type-check

Pre-push Hook

#!/bin/sh
# .husky/pre-push
npm run lint -- --max-warnings 0
npm run type-check

Gradual Strictness Adoption

Phase 1: Warnings

rules: {
  '@typescript-eslint/no-explicit-any': 'warn',
  '@typescript-eslint/strict-boolean-expressions': 'warn',
  'complexity': 'warn',
}

Phase 2: Errors for New Code

rules: {
  '@typescript-eslint/no-explicit-any': 'error',
  // ... but allow in legacy files
}

// .eslintignore-legacy
src/legacy/**

Phase 3: Full Strictness

Remove legacy ignores and enforce everywhere.


Interview Questions

1. How do you introduce strict linting to an existing codebase?

  1. Start with warnings, not errors
  2. Use --max-warnings to prevent new issues
  3. Fix incrementally by directory/feature
  4. Use git hooks to prevent new violations
  5. Track metrics (violation count over time)

2. Which rules have the highest impact on code quality?

  • @typescript-eslint/no-explicit-any - Forces proper typing
  • @typescript-eslint/strict-boolean-expressions - Prevents truthy/falsy bugs
  • @typescript-eslint/no-floating-promises - Catches unhandled async
  • complexity - Limits function complexity
  • import/no-cycle - Prevents circular dependencies

3. How do you handle rules that conflict with your codebase style?

  1. Customize rule options (many rules have configuration)
  2. Use overrides for specific file patterns
  3. Disable with inline comments sparingly (with justification)
  4. Create team consensus on which rules to modify