Next.js + Tailwind CSS + TypeScript + ESLint + Prettier

initialize project

nextjs installation

# npx create-next-app@latest

package.json

{
  "name": "feng-wei-frontend-tech-blog",
  "version": "1.0.0",
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint",
    "prepare": "husky"
  },
  "engines": {
    "node": ">=20.17.0",
    "npm": "10.8.2",
    "yarn": ">=1.22.19"
  },
  "private": true,
  "dependencies": {
    "next": "^15.0.1",
    "react": "^18.0.0",
    "react-dom": "^18.0.0"
  },
  "devDependencies": {
    "@types/node": "^20",
    "@types/react": "^18",
    "@types/react-dom": "^18",
    "eslint": "^8",
    "eslint-config-next": "^15.0.1",
    "eslint-config-prettier": "^8.5.0",
    "eslint-plugin-prettier": "^4.0.0",
    "eslint-plugin-simple-import-sort": "^12.1.1",
    "husky": "^9.1.6",
    "lint-staged": "^15.2.10",
    "postcss": "^8",
    "prettier": "^2.8.4",
    "sass": "^1.50.0",
    "tailwindcss": "^3.4.1",
    "typescript": "^5"
  }
}

.nvmrc

20.17.0

.eslintrc

Inherits Next.js and TypeScript rules, and uses Prettier for code formatting. Airbnb rules are not needed, because some rules are conflicted with next/core-web-vitals and next/typescript.

{
  "plugins": ["prettier", "simple-import-sort"],
  "extends": ["next/core-web-vitals", "next/typescript", "prettier"],
  "rules": {
    "prettier/prettier": 2,
    "no-shadow": 2,
    "import/no-unresolved": [
      2,
      {
        "ignore": ["^swiper", "^echarts"]
      }
    ],
    "import/newline-after-import": 2,
    "import/no-extraneous-dependencies": [
      2,
      {
        "devDependencies": true
      }
    ],
    "jsx-a11y/click-events-have-key-events": 0,
    "jsx-a11y/no-static-element-interactions": 0,
    "no-console": [
      2,
      {
        "allow": ["info", "warn", "error"]
      }
    ],
    "no-nested-ternary": 2,
    "no-param-reassign": 2,
    "padding-line-between-statements": [
      2,
      { "blankLine": "always", "prev": ["const", "let", "var"], "next": "*" },
      { "blankLine": "any", "prev": ["const", "let", "var"], "next": ["const", "let", "var"] }
    ],
    "simple-import-sort/imports": [
      2,
      {
        "groups": [
          // Side effect imports.
          ["^\\u0000"],
          // Third packages. `react` related packages come first.
          ["^react", "^@?\\w"],
          // Internal packages.
          ["^@/\\w"],
          // Parent imports. Put `..` last. Other relative imports. Put same-folder imports and `.` last.
          ["^\\.\\.(?!/?$)", "^\\.\\./?$", "^\\./(?=.*/)(?!/?$)", "^\\.(?!/?$)", "^\\./?$"],
          // Css relative imports.
          ["^.+\\.(css|less|scss)$"]
        ]
      }
    ],
    "no-bitwise": 1,
    "@typescript-eslint/no-explicit-any": 1
  }
}

.eslintignore

.next
dist
node_modules/
/.idea
.DS_Store

.prettierrc

{
  "tabWidth": 2,
  "printWidth": 100,
  "semi": false,
  "singleQuote": true,
  "arrowParens": "avoid",
  "trailingComma": "none"
}

tsconfig.json

the include options are used to specify the files to be included from the compilation process. if you want to include some files in the project, you can use "xxxfile/**/*.ts"

{
  "compilerOptions": {
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "bundler",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": true,
    "plugins": [
      {
        "name": "next"
      }
    ],
    "paths": {
      "@/*": ["./*"]
    }
  },
  "include": ["next-env.d.ts", ".next/types/**/*.ts", "*.tsx", "*.ts"],
  "exclude": ["node_modules"]
}

lint-staged

first, install lint-staged and husky

# yarn add --dev lint-staged husky

then, initialize husky, will create .husky folder and pre-commit.sh file

# npx husky init

in .husky/pre-commit.sh, add this command

#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"  # This loads nvm

# use project specified node version
nvm use

# run lint-staged
npx lint-staged

add .lintstagedrc file

const path = require('path')

const buildEslintCommand = filenames =>
  `next lint --fix --file ${filenames.map(f => path.relative(process.cwd(), f)).join(' --file ')}`

module.exports = {
  'app/**/*.{js,jsx,ts,tsx}': [buildEslintCommand],
  'components/**/*.{js,jsx,ts,tsx}': [buildEslintCommand]
}