As a Frontend-Backend Engineer, I've seen projects struggle with code consistency and quality. Manual code reviews can't catch everything, and style discussions waste precious development time. The solution? Robust code quality automation that works seamlessly in your workflow.
The Modern Code Quality Stack
The holy trinity of automated code quality consists of:
- ESLint: Error detection and code quality rules
- Prettier: Consistent code formatting
- Custom Rules: Domain-specific requirements
Let's dive into setting up a production-ready system.
Advanced ESLint Configuration
Skip the basic .eslintrc.js
approach. Modern projects need modular, environment-aware configurations:
// eslint.config.js (ESLint 9+ flat config)
import js from '@eslint/js';
import typescript from '@typescript-eslint/eslint-plugin';
import react from 'eslint-plugin-react';
import security from 'eslint-plugin-security';
export default [
js.configs.recommended,
// TypeScript files
{
files: ['**/*.{ts,tsx}'],
languageOptions: {
parser: '@typescript-eslint/parser',
parserOptions: {
project: './tsconfig.json',
},
},
plugins: { '@typescript-eslint': typescript },
rules: {
'@typescript-eslint/no-unused-vars': 'error',
'@typescript-eslint/explicit-function-return-type': 'warn',
'@typescript-eslint/prefer-nullish-coalescing': 'error',
},
},
// React components - accessibility focus
{
files: ['src/components/**/*.{jsx,tsx}'],
plugins: { react },
rules: {
'react/prop-types': 'off',
'react-hooks/exhaustive-deps': 'error',
'jsx-a11y/alt-text': 'error',
},
},
// API routes - security focus
{
files: ['src/api/**/*.{js,ts}'],
plugins: { security },
rules: {
'security/detect-object-injection': 'error',
'no-console': 'error',
},
},
];
Prettier Integration Without Conflicts
The secret to ESLint-Prettier harmony is proper configuration:
// .prettierrc.js
module.exports = {
semi: true,
singleQuote: true,
trailingComma: 'es5',
printWidth: 80,
overrides: [
{
files: '*.{ts,tsx}',
options: {
trailingComma: 'all',
},
},
],
};
// In your ESLint config
{
plugins: { prettier: require('eslint-plugin-prettier') },
extends: ['prettier'], // Disables conflicting rules
rules: {
'prettier/prettier': 'error',
},
}
Building Custom ESLint Rules
Sometimes you need domain-specific rules. Here's a custom rule that enforces consistent error handling:
// rules/consistent-error-handling.js
module.exports = {
meta: {
type: 'problem',
fixable: 'code',
messages: {
inconsistentError: 'Error "{{ errorName }}" should start with "error" or "err"',
},
},
create(context) {
return {
CatchClause(node) {
const param = node.param;
if (param && param.type === 'Identifier') {
const errorName = param.name;
const validPatterns = ['error', 'err', 'e'];
if (!validPatterns.some(pattern =>
errorName.toLowerCase().startsWith(pattern))) {
context.report({
node: param,
messageId: 'inconsistentError',
data: { errorName },
fix(fixer) {
return fixer.replaceText(param, 'error');
},
});
}
}
},
};
},
};
CI/CD Integration That Actually Works
Automate quality checks in your pipeline:
# .github/workflows/code-quality.yml
name: Code Quality
on: [push, pull_request]
jobs:
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
- run: npm ci
- name: ESLint with SARIF
run: |
npx eslint . \
--format=@microsoft/eslint-formatter-sarif \
--output-file=eslint-results.sarif \
--max-warnings=0
- name: Upload to GitHub Security
uses: github/codeql-action/upload-sarif@v2
if: always()
with:
sarif_file: eslint-results.sarif
- name: Prettier Check
run: npx prettier --check .
Pre-commit Hooks for Instant Feedback
// package.json
{
"lint-staged": {
"*.{js,jsx,ts,tsx}": [
"eslint --fix --max-warnings=0",
"prettier --write"
],
"*.{json,md,yml}": ["prettier --write"]
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
}
}
Performance Optimization Tips
Large codebases need optimized linting:
// Fast rules (prefer these)
'prefer-const': 'error',
'no-var': 'error',
'eqeqeq': 'error',
// Expensive rules (use sparingly)
'@typescript-eslint/no-floating-promises': 'warn', // Requires type checking
'import/no-cycles': 'warn', // Can be very slow
// Use ignores for better performance
ignores: [
'node_modules/**',
'dist/**',
'*.min.js',
'**/*.d.ts', // Skip unless necessary
],
Gradual Adoption Strategy
Introduce new rules progressively:
const LINT_PHASE = process.env.LINT_PHASE || 'introduction';
const ruleLevel = {
introduction: 'warn', // Week 1-2: Show warnings
enforcement: 'error', // Week 3-4: Block new violations
strict: 'error', // Week 5+: Fix all violations
}[LINT_PHASE];
export default [{
rules: {
'prefer-const': ruleLevel,
'complexity': [ruleLevel, { max: 15 }],
},
}];
The Bottom Line
Effective code quality automation isn't about having the most rules—it's about having the right rules that:
- Catch real problems before they reach production
- Enforce team standards without slowing development
- Scale with your codebase and team size
- Integrate seamlessly into your workflow
Start with a solid ESLint + Prettier foundation, add context-aware rules for different parts of your application, and gradually introduce custom rules for your specific domain needs.
Your future self (and your teammates) will thank you when code reviews focus on logic and architecture instead of style discussions.
What's your biggest code quality challenge? Share your experience in the comments below! 👇