Building the Best Next.js TypeScript Standard Vitest ESLint Configuration
Continuing from our previous discussion on establishing a robust structure for React projects using an Atomic Module Component approach, we delve deeper into en...
Continuing from our previous discussion on establishing a robust structure for React projects using an Atomic Module Component approach, we delve deeper into enhancing our development workflow with the integration of Next.js, TypeScript, and Vitest. In this post, we’ll focus on configuring ESLint to ensure code consistency, quality, and adherence to best practices.
Previous post: About project structure
Why ESLint?
ESLint is a powerful tool for identifying and fixing common programming errors and enforcing code style conventions. By integrating ESLint into our Next.js TypeScript project, we can catch potential issues early in the development process, maintain consistent code style across the codebase, and improve overall code quality.
A good ESLint configuration file will save us time during code reviews with arbitrary code formatting issues.
Setting Up ESLint
To begin, make sure you have ESLint installed in your project. You can do this by running:
npm install eslint --save-dev
# or
yarn add eslint --devNext, let’s configure ESLint to work perfectly with Next.js, TypeScript, and Vitest. We will take advantage of the ESLint configuration that I recommend to initialize a project and that can later be adjusted to the needs of each project, which includes plugins and rules designed for this stack.
Starting with installing the dependencies of this ESLint configuration.
npm install --save-dev \
eslint \
eslint-config-standard \
eslint-plugin-promise \
eslint-plugin-import \
eslint-plugin-n \
eslint-plugin-react \
typescript \
typescript-eslint \
eslint-plugin-tailwindcss \
eslint-plugin-testing-library \
@stylistic/eslint-plugin \
eslint-plugin-vitest \
eslint-plugin-react-hooks \
eslint-plugin-jsx-a11y \
eslint-plugin-simple-import-sort \
eslint-config-next \
@typescript-eslint/eslint-plugin \
eslint-plugin-unused-importsOr
yarn add --dev eslint eslint-config-standard eslint-plugin-promise eslint-plugin-import eslint-plugin-n eslint-plugin-react typescript typescript-eslint eslint-plugin-tailwindcss eslint-plugin-testing-library @stylistic/eslint-plugin eslint-plugin-vitest eslint-plugin-react-hooks eslint-plugin-jsx-a11y eslint-plugin-simple-import-sort eslint-config-next @typescript-eslint/eslint-plugin eslint-plugin-unused-importsCreate the .eslintrc.cjs file with the following configuration.
// .eslintrc.cjs
module.exports = {
env: {
browser: true,
es2024: true
},
globals: {
process: true,
React: true,
JSX: true
},
ignorePatterns: ['*.md'],
extends: [
'standard',
'eslint:recommended',
'plugin:react/recommended',
'plugin:@next/next/recommended',
'plugin:tailwindcss/recommended',
'plugin:testing-library/react',
'plugin:vitest/recommended',
'plugin:react-hooks/recommended',
'plugin:@typescript-eslint/recommended',
'plugin:@stylistic/all-extends'
],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaFeatures: {
jsx: true
},
ecmaVersion: 'latest',
sourceType: 'module'
},
plugins: [
'simple-import-sort',
'jsx-a11y',
'unused-imports',
'vitest',
'testing-library',
'@stylistic',
'@typescript-eslint'
],
rules: {
'react/jsx-max-depth': [
'warn',
{ max: 7 }
],
'react/prop-types': 0,
'unused-imports/no-unused-imports': 'error',
'testing-library/prefer-screen-queries': 0,
'testing-library/no-manual-cleanup': 0,
indent: 0,
'react-hooks/exhaustive-deps': 0,
'@stylistic/indent': [
'error',
2
],
'@stylistic/quote-props': [
'error',
'as-needed'
],
'@stylistic/quotes': [
'error',
'single'
],
'@stylistic/semi': [
'error',
'never'
],
'@stylistic/object-curly-spacing': [
'error',
'always'
],
'@stylistic/padded-blocks': [
'error',
'never'
],
'@stylistic/arrow-parens': [
'error',
'as-needed'
],
'@stylistic/dot-location': [
'error',
'property'
],
'@stylistic/function-call-argument-newline': [
'error',
'never'
],
'@stylistic/object-property-newline': [
'error',
{ allowAllPropertiesOnSameLine: true }
],
'@stylistic/multiline-ternary': [
'error',
'always-multiline'
],
'@stylistic/member-delimiter-style': 'off',
'@stylistic/no-extra-parens': 'off',
'simple-import-sort/imports': [
'error',
{
groups: [
// Packages `react` related packages come first.
['^react'],
['^next'],
// Themes
['^(@/themes)'],
// Internal packages.
['^(@/atoms)(/.*|$)'],
['^(@/molecules)(/.*|$)'],
['^(@/organisms)(/.*|$)'],
['^(@/layouts)(/.*|$)'],
['^(@/lib)(/.*|$)'],
['^(@/store)(/.*|$)'],
['^(@/hooks)(/.*|$)'],
['^(@/models)(/.*|$)'],
['^(@/utils)(/.*|$)'],
['^(@/mocks)(/.*|$)'],
// Side effect imports.
['^\\u0000'],
// Parent imports. Put `..` last.
[
'^\\.\\.(?!/?$)',
'^\\.\\./?$'
],
// Other relative imports. Put same-folder imports and `.` last.
[
'^\\./(?=.*/)(?!/?$)',
'^\\.(?!/?$)',
'^\\./?$'
],
// Style imports.
['^.+\\.?(css)$']
]
}
]
},
settings: {
react: {
version: 'detect'
}
}
}This configuration will be old/deprecated with the release of ESLint 9.0, it is not yet available or stable, when it is usable I will possibly make a post updating the configuration to the new format which is much better and I have been waiting for many years to have something like this.
Why This Configuration?
This ESLint configuration combines industry-standard rules with plugins specifically tailored for Next.js, TypeScript, and Vitest projects. Here’s a breakdown of some key components:
- eslint:recommended : Includes a set of rules that are recommended for all ESLint users.
- plugin:@next/next/recommended: Provides recommended Next.js specific rules.
- plugin:@typescript-eslint/recommended: Offers recommended TypeScript-specific rules.
- plugin:vitest /recommended: Includes recommended rules for Vitest.
- plugin:@stylistic/all-extends: Incorporates additional stylistic rules for improved code consistency.
By using this configuration, you can ensure that your Next.js TypeScript project follows best practices, maintains consistent code style, and leverages the power of ESLint for identifying and fixing potential issues.
Why Not Prettier?
In the JavaScript ecosystem, Prettier is another popular tool for code formatting. However, in some scenarios, you might choose not to use Prettier alongside ESLint. Here’s an example of how you can configure Visual Studio Code to use ESLint for formatting:
.vscode/settings.json
// .vscode/settings.json
{
"editor.defaultFormatter": "dbaeumer.vscode-eslint",
"editor.formatOnSave": true,
"editor.formatOnPaste": true,
"[typescriptreact]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
},
"files.exclude": {
"**/.git": true,
"**/.svn": true,
"**/.hg": true,
"**/CVS": true,
"**/.DS_Store": true,
"**/Thumbs.db": true,
"**/node_modules": false
},
"[typescript]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
},
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact"
]
// Other settings...
}By configuring Visual Studio Code to use ESLint as the default formatter, you can ensure consistent code formatting while leveraging ESLint’s linting capabilities.
Conclusion
Incorporating ESLint into your Next.js TypeScript project is a crucial step towards improving code quality, maintainability, and developer productivity. By following the configuration outlined in this post, you can establish a solid foundation for your project and ensure that your codebase adheres to industry standards and best practices.
In the next post, we’ll dive deeper into optimizing our development workflow with additional tooling and techniques. Stay tuned for more insights and tips on building better React applications!
Note: I do not recommend making the eslint change and the eslint fixes in the same PR. In my case, I handled it this way for the simplicity of the post.