Yevhen Klymentiev
dark
light
console
darkness
y.klymentiev@gmail.com
Reusable SnippetsPractical utility code for everyday use — custom-built and ready to share

FE Next.js (TS)

Step-by-step setup for a Next.js (TypeScript) project with ESLint v9 flat config, typescript-eslint via next/typescript, Prettier, Stylelint, Jest via next/jest, Husky v9, and Semantic Release.

1. Install dev dependencies

File
Copied!
1npm install -D \
2  typescript-eslint @eslint/eslintrc \
3  eslint-config-prettier eslint-plugin-prettier \
4  prettier \
5  stylelint stylelint-config-standard \
6  jest @types/jest \
7  @testing-library/react @testing-library/jest-dom @testing-library/user-event \
8  husky lint-staged \
9  @commitlint/config-conventional @commitlint/cli \
10  semantic-release @semantic-release/changelog @semantic-release/git \
11  @semantic-release/github @semantic-release/commit-analyzer \
12  @semantic-release/release-notes-generator

Note: eslint, eslint-config-next, and typescript are already included when scaffolding with create-next-app.

2. Create eslint.config.mjs

JavaScript
Copied!
1import { dirname } from 'path';
2import { fileURLToPath } from 'url';
3import { FlatCompat } from '@eslint/eslintrc';
4import prettierConfig from 'eslint-config-prettier';
5import prettier from 'eslint-plugin-prettier';
6
7const __filename = fileURLToPath(import.meta.url);
8const __dirname = dirname(__filename);
9const compat = new FlatCompat({ baseDirectory: __dirname });
10
11export default [
12  ...compat.extends('next/core-web-vitals', 'next/typescript'),
13  {
14    plugins: { prettier },
15    rules: {
16      '@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
17      'prettier/prettier': 'error',
18    },
19  },
20  prettierConfig,
21];

Note: next/typescript extends @typescript-eslint/recommended rules. It replaces the separate @typescript-eslint/eslint-plugin and @typescript-eslint/parser packages.

3. Update tsconfig.json

JSON
Copied!
1{
2  "compilerOptions": {
3    "paths": {
4      "@/*": ["./*"]
5    }
6  }
7}

4. Create .prettierrc

JSON
Copied!
1{
2  "singleQuote": true,
3  "trailingComma": "all",
4  "printWidth": 100,
5  "tabWidth": 2,
6  "semi": true
7}

5. Create stylelint.config.mjs

JavaScript
Copied!
1export default {
2  extends: ['stylelint-config-standard'],
3  rules: {
4    'at-rule-no-unknown': [
5      true,
6      {
7        ignoreAtRules: ['tailwind', 'apply', 'variants', 'responsive', 'screen'],
8      },
9    ],
10    'no-descending-specificity': null,
11  },
12};

Note: stylelint-config-prettier is no longer needed — Stylelint v15+ removed all formatting rules by default.

6. Configure Husky and lint-staged

Add to package.json:

JSON
Copied!
1{
2  "scripts": {
3    "prepare": "husky"
4  },
5  "lint-staged": {
6    "*.{ts,tsx}": ["eslint --fix", "prettier --write"],
7    "*.{css,scss}": ["stylelint --fix", "prettier --write"],
8    "*.{json,md,yml}": ["prettier --write"]
9  }
10}

Initialize Husky and set up hooks:

File
Copied!
1npx husky init
2echo "npx lint-staged" > .husky/pre-commit
3echo "npx --no -- commitlint --edit \"$1\"" > .husky/commit-msg

Note: Husky v9 replaced husky install with husky init. The prepare script is now just "husky".

7. Create commitlint.config.js

JavaScript
Copied!
1module.exports = {
2  extends: ['@commitlint/config-conventional'],
3};

8. Create jest.config.js

JavaScript
Copied!
1const nextJest = require('next/jest');
2const createJestConfig = nextJest({ dir: './' });
3
4module.exports = createJestConfig({
5  testEnvironment: 'jsdom',
6  setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
7  moduleNameMapper: {
8    '^@/(.*)$': '<rootDir>/$1',
9  },
10  testPathIgnorePatterns: ['/node_modules/', '/.next/'],
11});

Note: next/jest uses SWC to transform TypeScript natively — no ts-jest, identity-obj-proxy, or Babel config needed.

9. Create jest.setup.ts

TypeScript
Copied!
import '@testing-library/jest-dom';

10. Create release.config.js

JavaScript
Copied!
1module.exports = {
2  branches: ['main'],
3  plugins: [
4    '@semantic-release/commit-analyzer',
5    '@semantic-release/release-notes-generator',
6    [
7      '@semantic-release/changelog',
8      {
9        changelogFile: 'CHANGELOG.md',
10        changelogTitle: '# Changelog',
11      },
12    ],
13    [
14      '@semantic-release/git',
15      {
16        assets: ['CHANGELOG.md', 'package.json', 'package-lock.json'],
17        message: 'chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}',
18      },
19    ],
20    '@semantic-release/github',
21  ],
22};

In CI/CD, make sure GITHUB_TOKEN is available in the environment.

11. Add npm scripts to package.json

JSON
Copied!
1{
2  "scripts": {
3    "lint": "eslint .",
4    "format": "prettier --write \"**/*.{ts,tsx,json,css,scss,md}\"",
5    "stylelint": "stylelint \"**/*.{css,scss}\" --cache",
6    "test": "jest",
7    "test:watch": "jest --watch",
8    "test:coverage": "jest --coverage",
9    "release": "semantic-release"
10  }
11}

12. Optional: VSCode settings

JSON
Copied!
1{
2  "editor.formatOnSave": true,
3  "editor.codeActionsOnSave": {
4    "source.fixAll.eslint": "explicit"
5  },
6  "eslint.validate": ["typescript", "typescriptreact"],
7  "prettier.enable": true
8}