Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save nurmdrafi/c7acf59763e9daa458c1a6c371bde539 to your computer and use it in GitHub Desktop.
Save nurmdrafi/c7acf59763e9daa458c1a6c371bde539 to your computer and use it in GitHub Desktop.
Next.js Project Setup with ESLint, TypeScript, AirBnb, Conventional Commit, Lint Staged and Husky

[https://www.npmjs.com/package/@commitlint/config-conventional]

Install

npm i --save-dev eslint-config-airbnb eslint-config-airbnb-typescript @typescript-eslint/eslint-plugin@latest @typescript-eslint/parser@latest @commitlint/config-conventional @commitlint/cli

// install husky
npm i --save-dev husky

// init husky
npx husky init

Skip Git commit hooks

git commit -m "commit message" --no-verify

.eslintrc.json

{
  "env": {
    "browser": true,
    "commonjs": true,
    "es2022": true,
    "node": true
  },
  "settings": {
    "react": {
      "version": "detect"
    }
  },
  "plugins": [
    "@typescript-eslint"
  ],
  "extends": [
    "next/core-web-vitals",
    "airbnb",
    "airbnb-typescript",
    "airbnb/hooks"
  ],
  "parserOptions": {
    "parser": "@typescript-eslint/parser",
    "project": "./tsconfig.json"
  },
  "rules": {
    // === React === /
    "react/forbid-prop-types": "error",
    "react/default-props-match-prop-types": "error",
    "react/self-closing-comp": "error",
    "react/no-unused-prop-types": "error",
    "react/jsx-key": "error",
    "react/no-unused-state": "error",
    "react/state-in-constructor": "error",
    "react/function-component-definition": "off",
    "react/require-default-props": "off",
    "react/no-array-index-key": "off",
    "react/no-unescaped-entities": "off",
    "react/no-unstable-nested-components": "off",
    "react/no-danger": "off", // allow for next.js
    // === React Hooks === //
    "react-hooks/rules-of-hooks": "error",
    "react-hooks/exhaustive-deps": "error",
    // === JSX === //
    "jsx-a11y/img-redundant-alt": "error",
    "jsx-a11y/anchor-is-valid": "error",
    "jsx-a11y/alt-text": "error",
    "jsx-a11y/mouse-events-have-key-events": "error",
    "jsx-a11y/no-static-element-interactions": "off",
    "jsx-a11y/no-noninteractive-element-interactions": "off",
    "jsx-a11y/click-events-have-key-events": "off",
    // === React-JSX === //
    "react/jsx-indent-props": "error",
    "react/jsx-curly-newline": "error",
    "react/jsx-equals-spacing": "error",
    "react/jsx-indent": "warn",
    "react/jsx-props-no-multi-spaces": "warn",
    "react/jsx-curly-brace-presence": "warn",
    "react/jsx-closing-bracket-location": "warn",
    "react/jsx-tag-spacing": "warn",
    "react/jsx-props-no-spreading": "off",
    "react/react-in-jsx-scope": "off",
    "react/jsx-closing-tag-location": "off",
    "react/jsx-boolean-value": "off",
    "react/jsx-wrap-multilines": "off",
    "react/jsx-no-useless-fragment": "off",
    "react/jsx-one-expression-per-line": "off",
    "react/jsx-max-props-per-line": "off",
    "react/jsx-curly-spacing": [
      2,
      "always",
      {
        "allowMultiline": true,
        "spacing": {
          "objectLiterals": "never"
        }
      }
    ],
    // === TypeScript === //
    "@typescript-eslint/return-await": "error",
    "@typescript-eslint/restrict-plus-operands": "error",
    "@typescript-eslint/no-shadow": "error", // *** recommanded ***
    "@typescript-eslint/object-curly-spacing": "warn",
    "@typescript-eslint/no-misused-promises": [
      "error",
      {
        "checksVoidReturn": false
      }
    ],
    "@typescript-eslint/no-unused-expressions": "warn",
    "@typescript-eslint/no-unused-vars": "off", // use "warn" when code
    "@typescript-eslint/indent": "warn",
    "@typescript-eslint/space-before-blocks": "warn",
    "@typescript-eslint/keyword-spacing": "warn",
    "@typescript-eslint/comma-spacing": "warn",
    "@typescript-eslint/space-infix-ops": "warn",
    "@typescript-eslint/prefer-as-const": "warn",
    "@typescript-eslint/no-explicit-any": "off",
    "@typescript-eslint/no-unsafe-member-access": "off",
    "@typescript-eslint/no-unsafe-argument": "off",
    "@typescript-eslint/no-unsafe-call": "off",
    "@typescript-eslint/no-unsafe-assignment": "off",
    "@typescript-eslint/no-unsafe-return": "off",
    "@typescript-eslint/restrict-template-expressions": "off",
    "@typescript-eslint/no-floating-promises": "off",
    "@typescript-eslint/ban-types": "off",
    "@typescript-eslint/no-use-before-define": "off",
    "@typescript-eslint/semi": "off",
    "@typescript-eslint/comma-dangle": "off",
    "@typescript-eslint/quotes": "off",
    "@typescript-eslint/naming-convention": "off", // *** recommanded ***
    "@typescript-eslint/no-var-requires": "off",
    // === Import === //
    "import/first": "error",
    "import/no-mutable-exports": "error",
    "import/no-useless-path-segments": "error",
    "import/no-named-as-default": "error",
    "import/no-duplicates": "error",
    "import/newline-after-import": "error",
    "import/no-extraneous-dependencies": "off",
    "import/order": "off",
    // === Others === //
    "function-paren-newline": "error",
    "function-call-argument-newline": "error",
    "spaced-comment": "error",
    "operator-linebreak": "error",
    "computed-property-spacing": "error",
    "array-callback-return": "error",
    "space-unary-ops": "error",
    "object-shorthand": "error",
    "key-spacing": "error",
    "quote-props": "error",
    "prefer-const": "error",
    "prefer-destructuring": "error",
    "prefer-template": "error",
    "prefer-regex-literals": "error",
    "prefer-promise-reject-errors": "error",
    "guard-for-in": "error",
    "one-var": "error",
    "no-cond-assign": "error",
    "no-sequences": "error",
    "no-unneeded-ternary": "error",
    "no-extra-boolean-cast": "error",
    "no-lonely-if": "error",
    "no-unsafe-optional-chaining": "error",
    "no-mixed-operators": "error",
    "no-confusing-arrow": "error",
    "no-plusplus": "error",
    "no-constant-condition": "error",
    "no-floating-decimal": "error",
    "eol-last": "error",
    "array-bracket-spacing": "error", // *** recommanded ***
    "space-in-parens": "error", // *** recommanded ***
    "template-curly-spacing": [
      "error",
      "always"
    ], // *** recommanded ***
    "no-tabs": [
      "error",
      {
        "allowIndentationTabs": true
      }
    ],
    "dot-notation": "warn",
    "implicit-arrow-linebreak": "warn",
    "padded-blocks": "warn",
    "no-multiple-empty-lines": "warn",
    "no-multi-spaces": "warn",
    "no-else-return": "off",
    "indent": [
      "warn",
      2
    ],
    "linebreak-style": [
      "warn",
      "unix"
    ],
    "semi": [
      "warn",
      "never"
    ],
    "no-param-reassign": [
      "warn",
      {
        "props": true,
        "ignorePropertyModificationsFor": [
          "state"
        ]
      }
    ],
    "jsx-quotes": "off",
    "max-len": "off",
    "object-curly-newline": "off",
    "arrow-body-style": "off",
    "arrow-parens": "off",
    "consistent-return": "off",
    "eqeqeq": "off",
    "no-console": ["error", { "allow": ["warn", "error"] }], // *** recommanded ***
    "no-bitwise": "off",
    "no-trailing-spaces": "off",
    "no-useless-return": "off",
    "no-underscore-dangle": "off",
    "no-nested-ternary": "off",
    "no-restricted-syntax": "off",
    "no-unused-vars": "off", // "@typescript-eslint/no-unused-vars"
    "no-return-await": "off" // "@typescript-eslint/return-await"
  }
}

.eslintignore

.eslintrc.json
tsconfig.json
*.config.js
.next
out
node_modules

.vscode/settings.json

{
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true
  },
  "eslint.format.enable": true, // on save fix format
  "eslint.validate": [
    "javascript",
    "javascriptreact",
    "typescript",
    "typescriptreact"
  ]
}

tsconfig.json

{
  "compilerOptions": {
    "allowJs": true,
    "allowUnreachableCode": false,
    "allowUnusedLabels": false,
    "declaration": true,
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "module": "esnext",
    "moduleResolution": "node",
    "noEmit": true,
    "noFallthroughCasesInSwitch": true,
    "noImplicitReturns": true,
    "pretty": true,
    "resolveJsonModule": true,
    "skipLibCheck": true,
    "strict": true,
    "target": "es2021",
    "incremental": true
  },
  "include": [
    "next-env.d.ts",
    "**/*.ts",
    "**/*.tsx",
    "**/*.js"
  ],
  "exclude": [
    "node_modules",
    ".next",
    "out"
  ]
}

commitlint.config.js

module.exports = {
  extends: ['@commitlint/config-conventional'],
  rules: {
    //   TODO Add Scope Enum Here
    // 'scope-enum': [2, 'always', ['yourscope', 'yourscope']],
    'type-enum': [
      2,
      'always',
      [
        'feat',
        'fix',
        'docs',
        'chore',
        'style',
        'refactor',
        'ci',
        'test',
        'revert',
        'perf',
        'vercel',
      ],
    ],
  },
};

next.config.js

// const { withSentryConfig } = require('@sentry/nextjs');

/** @type {import('next').NextConfig} **/
// Next Js Config
const nextConfig = {
  reactStrictMode: true,
  images: { unoptimized: true },
  eslint: {
    dirs: ['.']
  }
  // sentry: {
  //   hideSourceMaps: true
  // }
}

module.exports = nextConfig
// module.exports = withSentryConfig(nextConfig)

.lintstagedrc

{
    "*.{js,jsx,ts,tsx}": [
        "npm run lint",
        "npm run check-types"
    ]
}

.husky/commit-msg

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

echo "Received commit message: $1"

NAME=$(git config user.name)
EMAIL=$(git config user.email)

if [ -z "$NAME" ]; then
  echo "empty git config user.name"
  exit 1
fi

if [ -z "$EMAIL" ]; then
  echo "empty git config user.email"
  exit 1
fi

git interpret-trailers --if-exists doNothing --trailer \
  "Signed-off-by: $NAME <$EMAIL>" \
  --in-place "$1"

npm exec --no -- commitlint --edit $1

.husky/pre-commit

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

# Get the branch name
BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD)

# This script runs linting, type checking, and builds before allowing a commit.
echo '🛠️  Preparing to commit: Running linting, type checking, and build...'

# Run ESLint for linting
echo "🔍 Running ESLint..."
npm run lint ||
(
  echo '❌ ESLint check failed. Please fix the linting issues before committing.'
  exit 1
)

# Run TypeScript type checking
echo "🔍 Running TypeScript type checking..."
npm run check-types ||
(
  echo '❌ TypeScript type checking failed. Please fix the type errors before committing.'
  exit 1
)

# Run Build test only for main and staging branch
if [ "$BRANCH_NAME" = "main" ] || [ "$BRANCH_NAME" = "staging" ]; then
  npm run build ||
  (
    echo '❌ Build failed. Please ensure the build process completes successfully before committing.'
    exit 1
  )
fi

# All checks passed, allow the commit
echo '✅ All pre-commit checks passed. You can proceed with the commit.'

package.json

  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint --dir .",
    "lint:fix": "npm run lint -- --fix",
    "prepare": "husky install",
    "check-types": "tsc --pretty --noEmit",
    "test-all": "npm run lint && npm run check-types && npm run build"
  }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment