How to Set Up ESLint, Prettier, StyleLint, and lint-staged in Next.js

Naveed AusafNaveed Ausaf
25 min read

A linter is a tool that scans code for potential issues. This is invaluable with a programming language like JavaScript which is so loosely typed.

Even for TypeScript, which is a strongly typed language whose compiler does a great job of detecting errors at compile time, linters such as ESLint have plugins that catch problems not caught by the compiler.

When you generate a new app using the Next.js CLI (npx create-next-app), ESLint is configured by default. But there are several problems with the linting setup generated by create-next-app:

  • If you choose SCSS for styling, you should use Stylelint in the build process to lint CSS or SCSS stylesheets. But it’s not set up automatically.

  • If instead you opt for Tailwind for styling, you should set up the Tailwind plugin for ESLint. But again, this isn’t done in the generated ESLint configuration.

  • If you choose TypeScript, then in Next.js v14 and below, TypeScript-specific ESLint rules are not configured, contrary to what the documentation states. While a Next.js v15 app has these set up, I would still tweak the setup further with the more powerful linting rules provided by the typescript-eslint project.

  • And finally, Prettier is not set up. Prettier is a code formatting tool. It can prevent inconsistently formatted code from getting into the code repository, which would make comparisons between different versions of the same file difficult. Also, nicely formatted code is easier to work with. So this is a pretty big omission.

In this tutorial, I'll show you how I set up linting and formatting in my Next.js projects in a way that addresses the issues above. I’ll also teach you how to install and configure some related VS Code extensions for coding assistance.

To follow along, you can either use a Next.js project you already have, or generate a new app by running npx create-next-app on the terminal.

If you’re scaffolding a new app, your choices are up to you (defaults are fine) but make sure to choose YES in response to the question about whether you’d like to use ESLint:

Terminal window in which Next.js scaffolder, create-next-app, is showing code generation options to the user

If you are following along with an existing app rather than a new one, upgrade it by running the following command in app root:

npm i next@latest react@latest react-dom@latest eslint-config-next@latest
npm i --save-dev eslint

This will avoid versioning conflicts down the line.

If you cannot upgrade to the latest version, you’ll need to specify versions for packages that will be installed in this tutorial to get around any version conflicts. Be warned that this can be frustrating.

Now you’re ready to open up the app in your code editor and proceed as follows.

Prerequisites

I assume that you know how to:

  • write a basic Next.js app with two or more pages.

  • install additional NPM packages into your app

Table of Contents

Set Up Prettier

Prettier is an opinionated code formatter that can format pretty much any file (.html, .json, .js, .ts, .css, .scss and so on).

Set it up in yuor app as follows:

  1. Install Prettier:

     npm install --save-dev prettier
    
  2. If you chose Tailwind for styling when generating the app, then install prettier-plugin-tailwindcss:

       npm install --save-dev prettier-plugin-tailwindcss
    

    This package is a Prettier plugin and provides rules for reordering of Tailwind classes used in a class or className attribute according to a canonical ordering. It helps keep the ordering of Tailwind classes used in the markup consistent.

    %[https://youtu.be/tQkBJXwzY8A?autoplay=1]

  3. Create .prettierrc.json in youyr project root. If you’re using SCSS for styling, paste the following snippet into this file:

     {
       "singleQuote": true,
       "jsxSingleQuote": true
     }
    

    If you’re using Tailwind instead, paste the following into .prettierrc.json:

     {
       "plugins": ["prettier-plugin-tailwindcss"],
       "singleQuote": true,
       "jsxSingleQuote": true
     }
    
  4. Create .prettierignore file in the app root, with the following content:

     node_modules
     .next
     .husky
     coverage
     .prettierignore
     .stylelintignore
     .eslintignore
     stories
     storybook-static
     *.log
     playwright-report
     .nyc_output
     test-results
     junit.xml
     docs
    

    This file ensure that files which are not app code (that is, those which are not .js, .ts, .css files and so on.) do not get formatted. Otherwise Prettier will end up spending too much time processing files whose formatting you don't really care about.

    'prettierignore (the file we just created), .eslintignore, and .stylelintignore have been ignored because these are plain text files with no structure so Prettier would complain that it cannot format them.

  5. Finally, I recommend that you follow the steps in this post to set LF as the EOL character, both in the repo and in your VS Code settings. Reasoning for this is given in the following subsection.

A note on line endings in Prettier

Prettier defaults to LF (Line Feed character) for line endings. This means that when it formats files, it will change all occurrences of the CRLF character sequence, if any, to LF.

LF is also the default in text editors and other tools in Unix-based systems (Linux, MacOS etc.). But on Windows, the default for line endings is CRLF (Carriage Return character, followed immediately by Line Feed character).

Windows tooling such as text and code editors can easily handle LF as line ending. But CRLF can be problematic for tools on Unix-based systems such as Linux and various flavours of Unix. Therefore it makes sense to only use LF as line endings in code as this would work on both Windows and Unix-based systems.

Configuring LF as the EOF character in Git repo and in code editors will bring your tooling in line with Prettier's default. It will also ensure that all files in the Git repo consistently have LF line endings. Thus if a contributor to your repo is on Windows which uses CRLF as EOL character, the code they add or modify in the repo would still use LF: the code editor would default new code files to LF; git commit` would convert any CRLFs to LF when committing.

Finally, setting LF as the line endings for the whole repo would avoid strange things that happen when on Windows, Prettier retains its default of LF but Git and your code editor continue to use their default of CRLF for line endings:

  • When VS Code Prettier extension formats a file (for example, when the extension is set up to "autoformat on save"), it does not change CRLF line endings. But formatting the same file by running Prettier on the command line does change line endings to LF. This discrepancy can be annoying.

  • Git may show warnings like this when when you run git add .:

    Warnings shown by git add command when some of the files being added contain LF but the repo's line ending default is CRLF

Set Up ESLint

Basics of ESLint configuration

ESLint comes with a number of linting rules out of the box. But you can also supplement these with ESLint plugins.

An ESLint plugin defines some linting rules. For example, if you look in the GitHub repo for Next's ESLint plugin, eslint-plugin-next, each file in the src/rules folder defines a linting rule as a TypeScript function. The index.js of the package then exports these rule functions in the rules object in its default export:

module.exports = {
  rules: {
    'google-font-display': require('./rules/google-font-display'),
    'google-font-preconnect': require('./rules/google-font-preconnect'),
    'inline-script-id': require('./rules/inline-script-id'),
    ...

The basic way to use these rules in your app is to install the plugin package, then reference it in the ESLint configuration file in the app's root folder.

For example, we can use rules from the eslint-plugin-next mentioned above by running npm install --save-dev eslint-plugin-next, then placing the following content in the ESLint config file .eslintrc.json in the app root:

{
    plugins: ["next"],
    "rules": {
        "google-font-display": "warning",
        "google-font-preconnect": "warning",
        "inline-script-id": "error",
    }
}

If you now run npx eslint . in your app's root folder, ESLint will lint every JavaScript file in the app against each of the three rules configured above.

There are three severities you can assign to a rule when configuring it for use: off, warning and error. As the snippet above shows, you enable a rule by assigning to it a severity of warning or error in the app's .eslintrc.json.

When referencing a plugin in your app's ESLint configuration file, the prefix eslint-plugin- in the plugin's package name is omitted. This is why the package that contains linting rules for Next.js, eslint-plugin-next, is referenced only as "next" in the snippet above.

Since it is quite cumbersome to configure a severity level - off, warning or error - for every single rule from every plugin that you want to use, the norm is to reference an ESLint configuration object, or ESLint config for short, that is exported by an NPM package. This is a JavaScript object that declares plugins and configures rules from these with severity levels just as we did above.

For example, the default export from eslint-plugin-next also contains several ESLint configs. Here is a another snippet from index.js of the plugin, this time showing exported ESLint configs in addition to the rules object for exporting rule functions:

module.exports = {
  rules: {
    'google-font-display': require('./rules/google-font-display'),
    'google-font-preconnect': require('./rules/google-font-preconnect'),
    'inline-script-id': require('./rules/inline-script-id'),
    ...
},    
configs: {
    recommended: {
      plugins: ['@next/next'],
      rules: {
        // warnings
        '@next/next/google-font-display': 'warn',
        '@next/next/google-font-preconnect': 'warn',
        ...

        // errors
        '@next/next/inline-script-id': 'error',
        '@next/next/no-assign-module-variable': 'error'
        ...

      }
    },
    'core-web-vitals': {
      plugins: ['@next/next'],
      extends: ['plugin:@next/next/recommended'],
      rules: {
        '@next/next/no-html-link-for-pages': 'error',
        '@next/next/no-sync-scripts': 'error',
      },
    },
}

As you can see, in addition to the rules (there are many more than those shown above), the plugin also exports two configs - recommended and core-web-vitals - that enable different selections of the rules defined in the plugin by assigning severity levels of error or warning to them.

The config that is normally used in Next.js projects is core-web-vitals. We can use this config object in our app’s ESLint configuration file (.eslintrc.json in app root) as follows:

{
  "extends": ["plugin:next/core-web-vitals"]
}

Thus is much simpler than declaring the plugin in plugins object and then assigning a severity level of error or warning to each rule from the plugin that we want to use.

Notice the difference between the configuration file - this is .eslintrc.json - and config - this is an object that configures some rules from a plugin for use in a client project by assigning severities to selected rules.

Contents of the configuration file are themselves a config. But in configuration files, we do not typically import a plugin and configure all rules from it that we want to use. Instead we almost always import a well-known/trusted config object that is exported by an NPM package. Such a config object - one that is exported by an NPM package for use in ESLint configuration files (in other packages/apps) - is also known as a shareable config.

Typically, plugins - these define ESLint rules as JavaScript/TypeScript functions - also bundle their rules into one or more shareable configs. The recommended config from plugin eslint-plugin-next that we used above is just one such config.

Shareable configs do not only come from plugin packages, although it is customary for plugins to also export one or more shareable configs composed of their own rules. Other packages, whose names begin with eslint-config- (as opposed to eslint-plugin-) can provide one or more named configs.

Next.js provides one such package named eslint-config-next. This re-exports configs recommended and core-web-vitals from the plugin. It also re-exports (in v15 and above of the package) a config of TypeScript linting rules from plugin typescript-eslint/eslint-plugin. So instead of using recommended config from the plugin like we have done above:

{
  "extends": ["plugin:next/core-web-vitals"]
}

we could have installed the package eslint-config-next and used that in .eslintrc.json:

{
  "extends": ["next/core-web-vitals"]
}

Since the package's name is not prefixed with plugin:, ESLint considers it to be a config package, reconstructing the name as eslint-config-next rather than as eslint-plugin-next. Notice how with config packages also, we delete the canonical prefix eslint-config- when referencing it in the ESLint configuration file.

It is possible to reference multiple shareable configs in extends. In this case, all the rules from all configs are used - except where there are multiple configs that each provide a rule with the same name. In this case the last config, proceeding left to right, wins. This is to say where there is a naming conflict, ESLint will use the rule from the last config on the list.

It is possible to use ESLint configuration file formats other than JSON. You can provide the same information as in an .eslintrc.json file in a JavaScript (.eslintrc.js or .eslintrc.cjs) or yaml (.eslintrc.yml or .eslintrc.yaml) file instead.

Also, ESLint has a new configuration file format often called flat config (which I haven't used here) where the config files are either JavaScript or TypeScript files.

Armed with an understanding of how to configure ESLint for use, you are ready to set up ESLint in your Next.js project. The sections below shows you how to do this.

ESLint Setup for TypeScript

If your app uses TypeScript, modify the ESLint configuration file (.esilntrc.json) as follows:

  1. On the terminal, in app's root folder, run the following command:

     npm install --save-dev @typescript-eslint/parser @typescript-eslint/eslint-plugin typescript
    

    @typescript-eslint/eslint-plugin provides a number of linting rules for TypeScript files, as well as shareable configs, that augment the checking that the TypeScript compiler does.

    @typescript-eslint/parser is a parser that allows ESLint to parse TypeScript files (by default it can only parser JavaScript files).

I am adding TypeScript compiler as a package - typescript - because typescript-eslint Getting Started instructions do the same.

  1. In app root folder, rename .eslintrc.json to .eslintrc.js. Then Replace contents of .eslintrc.js in app root with the following:

     /* eslint-env node */
     module.exports = {
       root: true,
       extends: [
         'next/core-web-vitals',
       ],
       plugins: ['@typescript-eslint', 'tailwindcss'],
       parser: '@typescript-eslint/parser',
       overrides: [
         {
           files: ['*.ts', '*.tsx'],
           parserOptions: {
             project: ['./tsconfig.json'],
             projectService: true,
             tsconfigRootDir: __dirname,
           },
           extends: [
             'next/core-web-vitals',
             'plugin:@typescript-eslint/recommended',
             //'plugin:@typescript-eslint/recommended-type-checked',
             // 'plugin:@typescript-eslint/strict-type-checked',
             // 'plugin:@typescript-eslint/stylistic-type-checked',
           ]
         },
       ],
     };
    

    This is what the various lines of this file do:

    /* eslint-env node */ stops ESLint from complaining that this is a CommonJS module. We have had to put this in because ESLint, as we have configured it here, does not allow CommonJS modules (which .eslintrc.js is, see module.exports = ... at the top) and expects modules in the project to be ES6.

root: true says this is the topmost ESLint configuration file even though there may be nested ESLint configs in subfolders.

extends: specifies various ESLint configs, each of which enables a collection of linting rules.

'next/core-web-vitals' is a config provided by eslint-config-next that bundles Next.js-specific rules (both for JavaScript and TypeScript, from an inspection of its code on GitHub).

The recommended-type-checked config (used in a nested extends within overrides object - this is explained shortly) is provided by @typescript-eslint/eslint-plugin. This plugin is part of the typescript-eslint project that publishes packages for linting rules and parsers to support linting of TypeScript files by ESLint.

The configs used is described here. It is a superset of the non-type checked versions of the config, recommended. It adds linting rules which use TypeScript's type checking API for additional type information. These rules are more powerful than those contained in the base, non-type-checked recommended config that only rely on the ESLint parser for TypeScript - package @typescript-eslint/parser.

You might prefer to use the strict-type-checked and stylistic-type-checked configs, also provided by @typescript-eslint/eslint-plugin. These are stricter than what I have used.

The least strict choice for TypeScript linting would probably be the recommended config. This is what is re-exported by eslint-plugin-next as config named typescript and is referenced in Next.js instructions for setting up ESLint with TypeScript as next/typescript (at least as of the time of this writing, September 2024). I prefer the config I have used instead.

parser: '@typescript-eslint/parser' specifies the ESLint TypeScript parser to be used instead of the default Espree parser which cannot parser TypeScript files.

parserOptions: tells the parser where to find the tsconfig.json file. This information allows the rules in the type-checked config used above - recommended-type-checked - to use TypeScript type checking APIs.

If we were using non-type-checked rules contained in other configs exported by the plugin, such as the recommended config, we would not need to provide this information.

plugins: ['@typescript-eslint'] : I don't know what the purpose of this line is. It shouldn't be necessary and I have tested that the given ESLint configuration works fine without it. But it doesn't do any harm and was contained in an example in the plugin's documentation from which I adapted the above config. So I have kept it.

The overrides section ensures that the TypeScript parser options that we’ve had to configure in order to support type-checked configs apply only to .ts and .tsx extensions (from this excellent StackOverflow answer). Otherwise, if parser and parserOptions objects had been at the top level, then running ESLint on the project would throw errors on .js files.

This is a problem as we have several .js config files including the .eslintrc.js itself, so there will be linting errors. We can avoid these errors by using the override.

ESLint Setup for Tailwind

If your app uses Tailwind, modify the config as follows:

  1. On the terminal, in app's root folder, run npm install --save-dev eslint-plugin-tailwindcss

  2. In ESLint config, add "plugin:tailwindcss/recommended" to the END of extends:

     {
       "extends": ["next/core-web-vitals", ..., "plugin:tailwindcss/recommended"],
    
     }
    
  3. In the ESLint config, add "tailwindcss" to plugins and add a rules object as shown below:

     {
       "plugins": [..., "tailwindcss"],
       "rules": {
         "tailwindcss/classnames-order": "off"
       },          
     }
    
  4. If your app uses TYPESCRIPT, then also add "plugin:tailwindcss/recommended" to inner extends inside overrides and duplicate the rules object inside overrides:

       {
         ...
         overrides: [
         {
            extends: ["next/core-web-vitals", ..., "plugin:tailwindcss/recommended"],
            rules: {
              'tailwindcss/classnames-order': 'off',
            },
         }
       }
    

In the Tailwind setup steps above, we have installed the package for the ESLint plugin for Tailwind, eslint-plugin-tailwindcss, and used the config recommended provided by the plugin.

eslint-plugin-tailwind provides some useful linting rules for Tailwind CSS classes used in HTML or JSX/TSX markup. The biggest one for me is that if a class used in code is not a Tailwind class, there would be a linting error. This makes sense as when I am using Tailwind, I only use Tailwind-generated classes and do not define my own CSS classes.

The plugin also has a rule that checks that the sequence of Tailwind class names used in the class or className attribute in markup follows a canonical ordering. But we installed prettier-plugin-tailwindcss in our Prettier configuration above which also reorders Tailwind class names. So we don’t need this rule in ESLint and it might conflict with what Prettier does in our workflow.

We’ll turn this rule off, which is named tailwindcss/classnames-order, in the configuration above by declaring the plugin in plugins object, then setting the rule to off in the rules object.

ESLint Setup for Prettier

  1. On the terminal run:

     npm install --save-dev eslint-config-prettier
    
  2. In ESLint config, add "prettier" to the END of extends:

     {
       "extends": ["next/core-web-vitals", ..., "prettier"]          
     }
    
  3. If your app uses TypeScript, then also add "plugin:tailwindcss/recommended" to the inner extends inside overrides also:

      {
        ...
        overrides: [
        {
           "extends": ["next/core-web-vitals", ..., "prettier"],
        }
      }
    

    In the Prettier setup steps above, the config referenced as prettier is the name of the NPM package eslint-config-prettier with eslint-config- deleted. The default export from the package is an entire ESLint config object and this is the config we want to use.

So in this case, we do not suffix the name prettier with /<name of config> as we have done when referencing the named config core-web-vitals from package eslint-config-next when we referenced is as next/core-web-vitals (see step 1 above).

This config switches off those rules in ESLint that conflict with the code formatting done by Prettier. This should be the last config in extends.

  1. Create .eslintignore in the project root. It doesn't need to have any content for now, but will come in handy in the future if ever you need to add folders or files that should be ignored by ESLint (see the final section of this post for an example).

Set Up Stylelint

Stylelint is a linter for CSS and SCSS stylesheets.

If you are using SCSS and NOT Tailwind, then set up Stylelint by following the instructions below. This set up will work for both CSS and SCSS files:

  1. On the terminal in project root run this command:

     npm install --save-dev sass
    

    Next.js has built-in SASS/SCSS support (so the Webpack config knows how to handle .scss and .sass files). But you still need to install a version of the sass package yourself, which is what we did above.

  2. Next, install packages for Stylelint and its rule configs:

     npm install --save-dev stylelint stylelint-config-standard-scss stylelint-config-prettier-scss
    

    Of these three packages:

  3. Now, create .stylelintrc.json in project root with the following contents:

     {
       "extends": [
         "stylelint-config-standard-scss",
         "stylelint-config-prettier-scss"
       ],
       "rules": {
         "selector-class-pattern": null
       }
     }
    

    The "extends" section declares the two Stylelint configs whose NPM packages we installed in the previous step.

The "rules" section is used to configure stylints rules. Here you can turn on or off, or configure the behavior of, individual Stylelint rules.

You can turn off a rule by setting it to null, as I have done for "selector-class-pattern". I turned it off because it insists on having CSS classes in the so called kebab case (for example, .panel-quiz instead of .panelQuiz). I find it inconvenient for various reasons so I turned it off.

  1. Next, create .stylelintignore in the project root with the following contents:

     styles/globals.css
     styles/Home.module.css
     coverage
    

    I created this file so that the two stylesheets generated by the Next.js CLI which do not comply with the linting rules can get ignored (there might be a better way of doing this but this works for me). Also, files in coverage folder do not need to be linted and would likely throw up errors.

Set Up package.json Scripts

  1. The most important script is "build". The default command for this script, next build, runs ESLint but not Prettier or (if you are using SCSS) Stylelint. So modify it in package.json file as follows:

If your app uses Tailwind, then:

    {
      "scripts": {
        "build": "prettier --check . && next build",
        ...

Otherwise, if your app uses SCSS, then:

    {
      "scripts": {
        "build": "prettier --check . && stylelint --allow-empty-input \"**/*.{css,scss}\" && next build",
        ...

With this tweak to the existing build script, we can run npm run build either locally or in a CI/CD pipeline and it will fail not only on ESLint failure (this was the case before) but also on Prettier formatting or Stylelint failure.

Indeed if you deploy your app to Vercel, the default pipeline there also calls npm run build. So when I introduced an error in one of my stylesheets, then deployed to Vercel, I got the following Stylelint error during deployment:

Stylelint errors when build script is run to build the app in Vercel's deployment pipeline

Note that I used the --check flag with prettier in the script (that is, I used the command prettier --check .). This runs Prettier in check mode, so it only checks for correct formatting and does not change the formatting.

I did this because the build script is what Vercel's deployment pipeline calls by default to build the code, and I don't want formatting to change during a CI build (nor do I want to tinker with Vercel defaults unless I absolutely have to).

To run Prettier locally to actually format the codebase, I define a separate build:local script which is same as build but runs Prettier without the --check flag, as well as a separate format script just to format with Prettier (but not build). These are set up below.

  1. Set up the "format" script in your package.json. This formats the codebase with Prettier and comes in handy every now and then:

     {
       "scripts": {
         ...
         "format": "prettier --write ."
    
  2. I recommend setting up a build:local script as follows:

If your app uses Tailwind, then:

    "build:local": "prettier --write . && next build"

Otherwise, if your app uses SCSS, then:

    "build:local": "prettier --write . && stylelint --allow-empty-input \"**/*.{css,scss}\" && next build"

Since we cannot format the code with Prettier prior to executing next build in the existing build script (for reasons described above), we can use this script locally to format code then lint and build in one go.

Set Up lint-staged

lint-staged is a package that you can use to run formatting and linting commands on staged files in a Git repo. Staged files are those that have been added to the Git index using git add .. These are the files that have changed since the last commit and will get committed when you next run git commit.

Husky is the typical choice in Node.js packages for registering commands to run in Git hooks. For example, registering the command npx lint-staged with Husky to run in the Git pre-commit hook means lint-staged will run automatically whenever you execute git commit.

At that time, the formatter (Prettier) and linters (ESLint or Stylelint) that have been configured to run in the lint-staged configuration file will run on the staged files. If there are any errors during formatting checks or linting, the commit will fail.

Whenever git commit fails due to linting errors, we can fix those, then run git add . and git commit again. Thus code only ever gets into the repo after it has been consistently formatted and verified to be free of linting errors. This is particularly advantageous in a team setting.

I prefer to only run prettier --check . on staged files. In particular, I do not change formatting of staged files and do not lint during a commit, for the following reasons:

Reason for not formatting code: I almost always build and test my code before committing. Any code formatting should have happened prior to or during this local build and test.

I find the idea that code going into my repo should change automatically just as it is being committed after I have ascertained that any code changes are good to go, a little bit unappealing.

Reason for not linting code: With TypeScript code, the compiler can catch a huge number of issues in code. The additional linting rules provided by eslint-typescript/eslint-plugin only supplement the checks made by the TypeScript compiler. So if I am linting code in staged files at commit time, I should build as well (so that the TypeScript compiler runs).

But building can be very time consuming on a large codebase. Besides, I almost always build and test (because of the way scripts in package.json have been set up above). Because of this, I don’t feel the need to repeat the lint and build process on staged files.

So, my personal preference is only to check for formatting on staged files, and neither reformat nor lint the code. This prevents inconsistently formatted code from getting into the Git repo where inconsistent formatting would make comparisons between different versions of the same file difficult.

So now, set up lint-staged and Husky as follows:

  1. Install the lint-staged package:

     npm install --save-dev lint-staged
    
  2. Create lint-staged.config.js in project root with the following contents:

     /* eslint-env node */
     const path = require('path');
     const formatCommand = 'prettier . --check';
    
     module.exports = {
       '*': formatCommand,
     };
    
  3. Install the Husky NPM package.

     npm install --save-dev husky
    
  4. Run the following on the terminal in app root to configure Husky to run lint-staged whenever git commit runs (in Git's pre-commit hook):

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

    You should now have a file .husy/pre-commit in your app's folder with only one line: npx lint-staged.

Set UP VS Code Extensions

If you use VS Code as your code editor, you can install the following VS Code extensions to provide linting and formatting on file save and syntax highlight on linting errors:

Put the following in a settings.json file in the .vscode folder in the project (you can of course put these settings in you User Preferences file also. You can access it from Command Palette Ctrl + P).

{
  "[javascript]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[typescript]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[typescriptreact]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[javascriptreact]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[scss]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "stylelint.validate": ["css", "scss"],
  "editor.formatOnSave": true
}

As they’re set up, the extensions will lint and format on Save.

Final Checks and Troubleshooting

Now it’s time to build and commit:

npm run format
npm run build
git add .
git commit -m "fix: set up linting and formatting"

Building and committing is a good sanity check for the setup we just did.

If anything had not been set up correctly, you might get errors either during build or at commit.

If you already had some code in the project, then there might be a few errors when you commit. Typically, these can be resolved by:

  • Adding folders or files to one of the *ignore files. For example, I already had some code in my project with Storybook installed. So I had to add folders .storybook and storybook-static to each of .stylelintignore, .eslintignore and .prettierignore as all three tools complained about them.

      stories
      storybook-static
    
  • Adding plugins for specific file types. For example, I had Gherkin .feature files in my project to describe integration tests. Prettier couldn't format these. So I added the prettier-plugin-gherkin by simply running:

      npm install  prettier-plugin-gherkin --save-dev
    

    Note that usually it is enough to install the package for a Prettier plugin for Prettier to locate it and additional configuration is not required.

    Likewise, ESLint complained when it encountered .cy.ts files containing Cypress interaction tests for my app. To resolve this linting error, I installed the NPM package for Cypress ESLint plugin and configured it as described here (unlike Prettier, to get this ESLint package to work, some configuration was required).

  • The typescript config might be too strict and there might be a lot of errors when you build, such as:

    ESLint error that occur on build when an ESLint config for TypeScript is used that is too strict.

    If you do not want to fix individual errors in your existing codebase, and they are too many to disable specific rules at error locations using ESLint comments (see below), then the simplest solution would be to disable the @typescript-eslint/recommended-type-checked config by commenting it out in .eslintrc.js and uncommenting @typescript-eslint/recommended which is less strict.

  • Sometimes it is safe to turn off a linting rule at a specific line or for a whole file. While I am always wary of doing this, in a (deliberately bad) experimental code file, I had many instances of an error that VS Code ESLint extension pointed. This was not caught before but was now being pointed out because strict TypeScript linting rules had been enabled:

    Intellisense in VS Code showing an ESLint error on a lint of code in a file.

    So I pressed Ctrl + . to Show Code Actions (I could instead have clicked the yellow lighbulb icon shown next to the issue), then selected “Disable @typescript/no-non-null-assertion for the entire file”.

    VS Code shows helpful tooltips when you click the bulb icon in the gutter. One of these allows y uoto disable an ESLint rule that is causing an error on the currently selected line of code.

    This placed the comment /* eslint-disable @typescript-eslint/no-non-null-assertion */ on top of my file to disable all instances of that particular error within the file:

    Special "eslint-disable" comment on top of a code file that disable a specific ESLint rule through the file.

Conclusion

This tutorial showed you how to configure linting and formatting tools in your Next.js app. I hope that it also gave you the background necessary both to understand the configurations given, and to customize them as needed.

0
Subscribe to my newsletter

Read articles from Naveed Ausaf directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Naveed Ausaf
Naveed Ausaf