Setting Up ESLint for Next.js 14 Projects

Anton Ioffe - November 11th 2023 - 9 minutes read

As the landscape of modern web development evolves, maintaining high standards for code quality and consistency becomes increasingly crucial. For teams leveraging the power of Next.js 14, ESLint stands as an indispensable tool in the journey towards immaculate codebases. In this comprehensive guide, seasoned developers will venture beyond basic linting practices, delving into the art of finessing their Next.js projects with tailored ESLint configurations, enhancing code style harmony through Prettier integration, and embracing the efficiency of pre-commit hooks. We'll also navigate the less-charted territories of performance, memory, and complexity to ensure your linting strategy is as performant as it is precise. Ready your IDEs – we're about to elevate your Next.js project to a paradigm of excellence in code quality.

Establishing a Robust Linting Environment in Next.js 14

Linting is an integral component of the development process, serving as an automated code quality tool, particularly within the context of Next.js 14. ESLint, embedded in this ecosystem, automates the detection of syntax errors, enforces coding standards, and identifies deviations from best practices. It offers real-time feedback directly within a developer's IDE, flagging code that could lead to potential issues, thereby improving maintainability and reducing bug introduction.

Starting with version 11, Next.js has integrated ESLint support to streamline and standardize code quality assurance. This built-in feature allows developers to use the next lint command, removing the need for complex configuration setup. The included rule set is tailored for the React framework, ensuring that developers automatically follow best practices around React hooks and JSX.

Integrating ESLint into a Next.js project conveys multiple advantages. It offloads the burden of combing through code for stylistic and logical errors from the developers. By delegating the task of spotting common pitfalls to ESLint, developers can maintain a consistent codebase, which becomes critical as projects grow and more collaborators come on board. This approach not only streamlines the development cycle but also ensures uniform adherence to coding conventions.

Incorporating ESLint promotes a proactive approach to code quality in Next.js 14 projects. It encourages developers to write clean, maintainable code, streamlining peer reviews and simplifying the integration of new contributors. ESLint serves not just as a tool for enhancing code quality but as a means of enabling developers to focus on delivering feature-rich, scalable applications.

Next.js 14's default configuration of ESLint is crucial; it reflects a set of standards concomitant with the prevailing React and Next.js best practices, providing the groundwork for a project characterized by high-functionality and fewer errors. Rather than being an optional add-on, ESLint is a fundamental aspect of the development setup in Next.js 14, ensuring that applications are resilient and prepared for future expansion.

Tailoring ESLint Configurations for Project Specifics

When setting up ESLint for a Next.js project, you may want to enforce TypeScript rules alongside the standard JavaScript rules. This ensures that both your .ts and .tsx files are linted according to TypeScript's best practices. Start by extending your .eslintrc configuration to include plugin:@typescript-eslint/recommended which adds TypeScript specific rules and next/core-web-vitals for Next.js optimizations.

{
  'extends': [
    'plugin:@typescript-eslint/recommended',
    'next',
    'next/core-web-vitals'
  ]
}

React projects, particularly those using Hooks, can benefit from linting rules that catch common mistakes in their usage. By using eslint-plugin-react-hooks, you can ensure that the Rules of Hooks are enforced, such as ensuring hooks are called in the same order each render.

{
  'extends': ['next'],
  'plugins': ['react-hooks'],
  'rules': {
    'react-hooks/rules-of-hooks': 'error',
    'react-hooks/exhaustive-deps': 'warn'
  }
}

Next.js has specific rules that can be incorporated through eslint-config-next. These rules help prevent common errors such as misuse of Image and Link components. For a tailored experience, you may selectively enable or disable certain rules based on the project's needs.

{
  'extends': ['next', 'next/core-web-vitals'],
  'rules': {
    // Override or add rules here
    'next/link-passhref': 'error',
    'next/no-img-element': 'off' // if you prefer to use <img> tags directly
  }
}

Custom rules can be essential in enforcing coding standards. For example, if your team prefers arrow functions for functional components, you can configure ESLint to warn whenever a function declaration is used for a React component. This helps maintain consistency across your codebase.

{
  'rules': {
    'react/function-component-definition': [
      'warn',
      {
        'namedComponents': 'arrow-function'
      }
    ]
  }
}

Finally, managing imports can become quite the task in large projects. ESLint can help sort and group your imports in a readable manner. The sort-imports and import/order rules can automatically organize your imports, dividing them into neat sections and sorting them alphabetically.

{
  'rules': {
    'sort-imports': ['error', {
      'ignoreCase': true,
      'ignoreDeclarationSort': true,
      'ignoreMemberSort': false
    }],
    'import/order': ['error', {
      'groups': ['builtin', 'external', 'internal'],
      'pathGroups': [
        {
          'pattern': 'react',
          'group': 'external',
          'position': 'before'
        }
      ],
      'newlines-between': 'always'
    }]
  }
}

This customization empowers developers to create an ESLint environment that reflects the unique practices of their team while still aligning with standard practices for TypeScript and React development.

Balancing Code Style with Prettier Integration

Integrating Prettier with ESLint leverages the strengths of both tools to achieve a harmonized coding environment that ensures both code quality and stylistic uniformity across a project. While ESLint focuses on the correctness aspects of the code, Prettier enforces style consistency, which is particularly important when working on large-scale applications or with teams. To align these tools, start by installing the necessary packages with yarn or npm: yarn add --dev prettier eslint-config-prettier eslint-plugin-prettier. These dependencies ensure the formatting powers of Prettier work seamlessly with the rule-enforcement of ESLint.

// package.json
// Add these lines to your devDependencies:
"devDependencies": {
  "eslint": "^7.32.0",
  "eslint-config-prettier": "^8.3.0",
  "eslint-plugin-prettier": "^3.4.0",
  "prettier": "^2.3.2",
  // ... other dependencies
}

The next step involves configuring .eslintrc.json to accommodate Prettier rules without causing conflicts. This is achieved by appending prettier to the extends array, ensuring that Prettier's rules take precedence, and by including prettier in the plugins section. Doing so deactivates any ESLint rules that might conflict with Prettier's style rules, allowing both tools to work in unison.

// .eslintrc.json
{
  "extends": [
    "next/core-web-vitals",
    // ... other extends
    "prettier" // Make sure this is the last item
  ],
  "plugins": [
    "prettier" // Enables eslint-plugin-prettier
  ],
  "rules": {
    "prettier/prettier": "warn" // Or "error" if you prefer
  }
}

For optimal configuration, create a .prettierrc.json file where custom formatting preferences can be established. This config overrides default styles and asserts the project's chosen conventions such as single quotes, tab widths, and trailing commas.

// .prettierrc.json
{
  "semi": false,
  "trailingComma": "es5",
  "singleQuote": true,
  "tabWidth": 2,
  "useTabs": false
}

Lastly, to automate and simplify usage, modify the scripts section of package.json to include commands for linting and formatting. This way, running a single command such as yarn lint or npm run lint will trigger ESLint checks, while yarn format or npm run format applies Prettier formatting across the codebase.

// package.json
"scripts": {
  "lint": "eslint '*/**/*.{js,jsx,ts,tsx}' --quiet",
  "format": "prettier --write '*/**/*.{js,jsx,ts,tsx,json,css,md}'"
}

Through this collaborative setup, developers can avoid the common pitfall of managing code correction and styling separately. Harmonizing ESLint with Prettier consolidates these aspects of development, ensuring that maintaining code excellence isn't just about the absence of errors but also the adherence to a consistent and professional style guide.

Optimizing Developer Experience with Pre-Commit Hooks

Pre-commit hooks serve as a gatekeeper for any code changes, nudging developers towards clean and error-free code submissions. The integration of Husky, an efficient tool for managing Git hooks, lies at the core of establishing these valuable checks. By installing Husky as a development dependency via yarn add --dev husky and activating it with yarn husky install, we smoothen the path towards automating code quality checks. Upon installation, Husky allows configuration of a variety of Git hooks, the most common being pre-commit, which checks for code hygiene before a commit is finalized.

With lint-staged, developers find a sharpened focus for linting and formatting efforts. A .lintstagedrc.js configuration file can be created to specify which file types to lint based on their extensions. In a typical Next.js project, this often involves targeting TypeScript and JavaScript files for ESLint checks, simultaneously applying Prettier for consistent formatting. For instance, specifying '**/*.(ts|tsx|js)': filenames => ['yarn eslint ' + filenames.join(' '), 'yarn prettier --write ' + filenames.join(' ')] in the configuration assures that only files staged for commit - and not the entire codebase - are processed.

The .husky/pre-commit file is where the action of lint-staged is triggered. This shell script must begin with the shebang #!/bin/sh and include the yarn lint-staged command to initiate linting and formatting. The magic of this setup is in its simplicity; when a commit is made, the pre-commit hook automatically runs to enforce code standards and best practices. If the hook identifies issues that violate these, it blocks the commit, prompting the developer to rectify the problems—thereby catching errors early and maintaining code quality.

In essence, this workflow is not only about preserving code integrity but also about enhancing the developer experience. It removes the cognitive load of keeping track of nit-picky details, allowing developers to concentrate on problem-solving and feature creation. Yet, it's important to note that setting up these tools should not impede the developer workflow. Striking the right balance between stringency of checks and workflow disruption is pivotal. A thoughtful consideration here is to ensure linting and formatting rules are agreed upon by the team, and the tools are configured accordingly to avoid unnecessary friction.

One must ponder, how does one reconcile the immediate feedback loop required by developers with stringent code quality checks, without sacrificing the efficiency of either? This question stirs a broader discussion on integrating such hooks into the development process in a way that developers perceive them as assistants rather than obstructions, fostering a culture of quality that enhances the overall development experience.

Performance, Memory, and Complexity Considerations

When configuring ESLint for a Next.js application, it's important to recognize that the tool's impact goes beyond enforcing code cleanliness; it also can affect your application's runtime performance, memory usage, and overall complexity. ESLint rules can be computationally expensive to enforce, especially those analyzing complex code patterns or deeply nested structures. The execution time for a full suite of ESLint checks might not seem significant on an individual developer's machine, but it compounds when scaled to continuous integration systems and large teams, potentially slowing down the development pipeline. Therefore, it's crucial to choose a well-considered ruleset that aligns with your project's requirements, without imposing unnecessary runtime penalties.

Memory consumption is another critical consideration. Certain ESLint rules necessitate maintaining an in-memory representation of the code's abstract syntax tree (AST), which can grow quite large for complex codebases. A bloated AST can lead to increased memory pressure and longer garbage collection cycles in the Node.js process that powers ESLint. Developers should thus be selective with rules that require complex pattern recognition or AST manipulation, as they could inadvertently introduce memory bloat.

The complexity introduced by an extensive list of ESLint rules is not just a matter of system resources but extends to cognitive overhead as well. A Next.js project jam-packed with linting rules can become harder to grasp, with developers spending more time triaging linting errors rather than writing new code. Such complexity may not only affect individual developers but also raise the bar for new contributors to effectively engage with the codebase. A sweet spot must be found between comprehensive linting and manageable rule complexity to foster a productive coding environment.

Regarding code maintenance and readability, ESLint rules can provide a framework for cleaner and more consistent code. However, this enforcement comes at a potential trade-off. Overly rigid rules might discourage certain code patterns that, while marginally less performant, offer superior readability and maintainability. Consider, for example, the use of higher-order functions which might be flagged for potential performance optimizations yet offer clear and reusable code structures. Balancing these ESLint rules to prioritize readable and maintainable code, without significantly degrading performance, is a nuanced endeavor and should be tailored to the nature of the project.

Inviting developers to scrutinize the cost-benefit ratio of each ESLint rule may lead to valuable discussions on the project's performance culture. A thought-provoking question might be: At what point does enforcing a particular code pattern cease to benefit the project, considering the trade-offs in performance and readability? Continuously re-evaluating your ESLint rules against the evolving needs and scale of your Next.js application is essential to maintaining an effective, high-performing codebase.

Summary

In this article, the author discusses the importance of setting up ESLint for Next.js 14 projects to maintain code quality and consistency. They provide guidance on tailoring ESLint configurations for project specifics, integrating Prettier for code style harmony, and optimizing the developer experience with pre-commit hooks. The article also highlights the considerations of performance, memory, and complexity when configuring ESLint, prompting developers to evaluate the cost-benefit ratio of each ESLint rule. The challenging task for the reader is to analyze their project's ESLint setup and determine if any rules can be optimized or removed to improve performance and readability without sacrificing code quality.

Don't Get Left Behind:
The Top 5 Career-Ending Mistakes Software Developers Make
FREE Cheat Sheet for Software Developers