🎯 Introduction

Setting up linting and formatting tools for a Next.js 15 project might seem like a Herculean task, but trust me, it’s easier than debugging a missing semicolon in production. 🫠

In this guide, I’ll walk you through configuring ESLint, Prettier, Lint-Staged, and Husky enterprise-style but without the soul-crushing complexity.

If you want clean, consistent code while ensuring no rogue console.log sneak into your main branch, then this setup is for you. With a streamlined configuration and automated enforcement, you'll catch issues early and maintain high-quality code effortlessly. Let’s dive in. 🚀


⚡ Step 1: Installing Dependencies

Let's kick things off by installing all the necessary dependencies in one swift command because manually installing them one by one is about as fun as debugging on a Friday night. 😵‍💫

This command sets up ESLint, Prettier, Lint-Staged, Husky, and a few handy plugins to keep your codebase in top shape. Run the following:

npm install --save-dev eslint @eslint/compat @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint-config-next eslint-config-prettier eslint-plugin-import eslint-plugin-prettier eslint-plugin-simple-import-sort eslint-plugin-unicorn husky lint-staged prettier prettier-plugin-tailwindcss

Troubleshooting Installation Issues

If you encounter dependency conflicts or peer dependency errors while running the above command, try installing with the --legacy-peer-deps flag:

npm install --save-dev --legacy-peer-deps eslint @eslint/compat @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint-config-next eslint-config-prettier eslint-plugin-import eslint-plugin-prettier eslint-plugin-simple-import-sort eslint-plugin-unicorn husky lint-staged prettier prettier-plugin-tailwindcss

This forces npm to ignore strict peer dependency requirements, which can sometimes resolve version conflicts. If the issue persists, consider using npm update or checking for specific version mismatches. 🚀


🛠️ Step 2: Configuring ESLint

Create a file named eslint.config.mjs in the root of your project and paste the following code.

This ESLint configuration is designed to enforce best practices, maintain consistent coding styles, and enhance code readability. Feel free to tweak it to fit your project's specific requirements.

import path from "node:path";
import { fileURLToPath } from "node:url";

import { fixupConfigRules } from "@eslint/compat";
import { FlatCompat } from "@eslint/eslintrc";
import js from "@eslint/js";
import typescriptEslintPlugin from "@typescript-eslint/eslint-plugin";
import typescriptEslintParser from "@typescript-eslint/parser";
import simpleImportSort from "eslint-plugin-simple-import-sort";
import unicornPlugin from "eslint-plugin-unicorn";

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const compat = new FlatCompat({
  baseDirectory: __dirname,
  recommendedConfig: js.configs.recommended,
  allConfig: js.configs.all,
});

const eslintConfig = [
  // Extending recommended ESLint configurations
  ...fixupConfigRules(
    compat.extends(
      "next/core-web-vitals",
      "plugin:import/recommended",
      "plugin:prettier/recommended",
    ),
  ),
  {
    // Registering plugins
    plugins: {
      "simple-import-sort": simpleImportSort,
      "@typescript-eslint": typescriptEslintPlugin,
      unicorn: unicornPlugin,
    },
    rules: {
      // Sorting imports automatically
      "simple-import-sort/exports": "error",
      "simple-import-sort/imports": "error",

      // Disabling certain unicorn rules that might be too restrictive
      "unicorn/no-array-callback-reference": "off",
      "unicorn/no-array-for-each": "off",
      "unicorn/no-array-reduce": "off",

      // Preventing overly strict abbreviation rules
      "unicorn/prevent-abbreviations": [
        "error",
        {
          allowList: {
            e2e: true,
          },
          replacements: {
            props: false,
            ref: false,
            params: false,
          },
        },
      ],
    },
  },
  {
    // TypeScript-specific settings
    files: ["**/*.ts", "**/*.tsx"],
    languageOptions: {
      parser: typescriptEslintParser,
      parserOptions: {
        project: "./tsconfig.json",
      },
    },
    rules: {
      "@typescript-eslint/no-unused-vars": "error",
      "@typescript-eslint/no-explicit-any": "warn",
      "unicorn/prefer-module": "off",
    },
  },
  {
    // JavaScript-specific settings
    files: ["**/*.js", "**/*.jsx"],
    rules: {
      "unicorn/prefer-module": "off",
    },
  },
];

export default eslintConfig;

🎨 Step 3: Configuring Prettier

Create a prettier.config.js file and paste this.

This Prettier configuration ensures consistent code formatting, improving readability and reducing unnecessary style debates in your team. Customize it as needed to fit your coding preferences.

module.exports = {
  arrowParens: "avoid", // Omit parentheses for single-parameter arrow functions
  bracketSameLine: false, // Keep closing brackets on a new line for JSX
  bracketSpacing: true, // Ensure spaces between brackets
  htmlWhitespaceSensitivity: "css", // Respect CSS for whitespace in HTML
  insertPragma: false,
  jsxSingleQuote: false, // Use double quotes in JSX
  plugins: ["prettier-plugin-tailwindcss"], // Enable Tailwind CSS formatting
  printWidth: 80, // Wrap lines at 80 characters for better readability
  proseWrap: "always",
  quoteProps: "as-needed", // Add quotes only when required
  requirePragma: false,
  semi: true, // Always use semicolons
  singleQuote: false, // Prefer double quotes
  tabWidth: 2, // Use 2 spaces per tab
  trailingComma: "all", // Use trailing commas where possible
  useTabs: false, // Use spaces instead of tabs
};

🔗 Step 4: Configuring Lint-Staged & Husky

Add the following to your package.json:

"scripts": {
  "dev": "next dev",
  "build": "next build",
  "start": "next start",
  "lint": "npx eslint ./src",
  "lint:fix": "npm run lint -- --fix",
  "format": "prettier . --check",
  "format:fix": "prettier . --write",
  "type-check": "tsc -b",
  "prepare": "husky",
  "lint-staged": "lint-staged"
},
"lint-staged": {
  "**/*": [
    "npm run format:fix",
    "npm run lint"
  ]
}

Now, let's set up a pre-commit hook using Husky:

npx husky init
echo "npm run lint-staged" > .husky/pre-commit

🎉 Conclusion

You now have a battle-hardened, enterprise-grade Next.js linting and formatting setup that will keep your code clean, maintainable, and bug-free! 🏆

🔥 Quick Recap of What We've Done:

  • Installed ESLint, Prettier, Lint-Staged, and Husky.
  • Configured ESLint with rules that enforce best practices.
  • Set up Prettier for consistent code formatting.
  • Integrated Lint-Staged to format and lint only staged files.
  • Configured Husky to enforce these checks before every commit.

🚀 With This Setup in Place:

  • No more accidental linting issues in production.
  • Your code is always formatted neatly.
  • Pre-commit hooks prevent bad commits.
  • Better collaboration and code consistency.

Whether you're working solo or in a team, this setup will help you stay productive and focus on building amazing features without worrying about messy code! 🚀