Prepare a new NextJS project 🍡

Β·

5 min read

πŸš€ Create a project with CLI

pnpm create next-app --use-pnpm --ts --eslint --src-dir
# or
npm create next-app --ts --eslint --src-dir
  • Choose TypeScript and ESLint tools.

  • If you set --experimental-app flag, you can meet the warning message below:

warn  - You have enabled experimental feature (appDir) in next.config.js.
warn  - Experimental features are not covered by semver, and may cause unexpected or broken application behavior. Use at your own risk.

πŸ—» Custom ESLint

This is the first look of default .eslintrc.json:

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

For React:

pnpm add -D eslint-plugin-react eslint-plugin-react-hooks

Add @typescript-eslint/eslint-plugin for TypeScript:

pnpm add -D @typescript-eslint/eslint-plugin

For prettier:

pnpm add -D eslint-plugin-prettier

For NextJS:

pnpm add -D eslint-plugin-next

.eslintrc.json:

{
  "parser": "@typescript-eslint/parser",
  "parserOptions": {
    "project": [
      "tsconfig.json"
    ],
    "createDefaultProgram": true
  },
  "plugins": [
    "@typescript-eslint",
    "prettier"
  ],
  "extends": [
    "eslint:recommended",
    "plugin:react/recommended",
    "plugin:@typescript-eslint/recommended",
    "plugin:@typescript-eslint/eslint-recommended",
    "plugin:@next/next/recommended",
    "next/core-web-vitals",
    "prettier"
  ],
  "rules": {
    "@typescript-eslint/no-unused-vars": 2,
    "@typescript-eslint/no-explicit-any": 2,
    "id-length": [
      2,
      {
        "exceptions": [
          "i"
        ]
      }
    ],
    "@typescript-eslint/naming-convention": [
      2,
      {
        "selector": "interface",
        "format": [
          "PascalCase"
        ],
        "custom": {
          "regex": "^I[A-Z]",
          "match": true
        }
      },
      {
        "selector": "variable",
        "format": [
          "camelCase",
          "UPPER_CASE"
        ]
      },
      {
        "selector": "typeParameter",
        "format": [
          "PascalCase"
        ],
        "prefix": [
          "T"
        ]
      },
      {
        "selector": "variable",
        "format": [
          "PascalCase"
        ],
        "types": [
          "boolean"
        ],
        "prefix": [
          "is",
          "should",
          "has",
          "can",
          "did",
          "will",
          "was"
        ]
      },
      {
        "selector": "enum",
        "format": [
          "PascalCase"
        ]
      },
      {
        "selector": "enumMember",
        "format": [
          "PascalCase",
          "camelCase"
        ]
      }
    ],
    "line-comment-position": [
      2,
      {
        "position": "above"
      }
    ]
  }
}

package.json :

{
  "scripts": {
    "lint:ts:fix": "eslint . --fix --ext .js,.ts,.tsx"
  }
}

✨ Add prettier

pnpm add -D prettier eslint-config-prettier

.prettierrc.yaml:

singleQuote: true
printWidth: 80

Verify all settings above:

pnpm lint

Add formatting script in package.json:

--- a/package.json
+++ b/package.json
@@ -6,7 +6,8 @@
     "dev": "next dev",
     "build": "next build",
     "start": "next start",
-    "lint": "next lint"
+    "lint": "next lint",
+    "format": "prettier --write --cache src"
   },
pnpm format

🌸 Add spell-checking

Install Code Spell Checker extension for VS Code and create cspell.json file with blank content.

πŸ‡ Add SASS

pnpm add -D sass

🌻 Add stylelint

pnpm add -D stylelint stylelint-checkstyle-formatter stylelint-config-recommended-scss stylelint-config-standard stylelint-scss

package.json:

{
  "scripts": {
    "lint:css": "stylelint --cache **/*.{scss,css}",
    "lint:css:fix": "stylelint --cache --fix **/*.{scss,css}"
  }
}

.stylelintignore :

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

.stylelintrc :

{
  "extends": "stylelint-config-recommended-scss",
  "formatter": "stylelint-checkstyle-formatter",
  "plugins": ["stylelint-scss"],
  "rules": {
    "string-quotes": "single",
    "color-hex-length": "long",
    "color-function-notation": "modern",
    "import-notation": "string"
  }
}

To verify the configuration above, run:

pnpm lint:css
pnpm lint:css:fix

β˜• Config the NodeJS version to develop

Add .nvmrc file with the content below or your own configuration:

v16.18.1

For example, use that configuration by using fnm:

fnm use

We can also use the package.json to verify the configuration above.

{
  "engines": {
    "node": ">=16",
    "pnpm": ">=7.26.0"
  }
}

πŸŒ’ Add environment variable for development and production

.env.development file's content:

# dev | test | prod
ENV=dev
NEXT_PUBLIC_ENV=dev

Setup the runtime environment variables:

cp .env.development .env.local

To define the type of this variable, create src/types/environment.d.ts:

/* eslint-disable @typescript-eslint/naming-convention */
export {};

declare global {
  namespace NodeJS {
    interface ProcessEnv {
      ENV: 'test' | 'dev' | 'prod';
      NEXT_PUBLIC_ENV: 'test' | 'dev' | 'prod';
    }
  }
}

β˜„οΈ Add the hook when committing the code

pnpm add -D husky lint-staged
pnpm husky install
touch .husky/pre-commit
chmod +x .husky/pre-commit

# lint the commit message
pnpm add -D @commitlint/cli @commitlint/config-conventional
pnpm husky add .husky/commit-msg  'pnpm --no -- commitlint --edit ${1}'

.husky/pre-commit file's content:

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

pnpm lint-staged

.husky/commit-msg file's content:

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

pnpm commitlint --edit ${1}

.lintstagedrc.js file's content:

const path = require('path');

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

const buildStyleLintCommand = (filenames) =>
  `stylelint --cache ${filenames
    .map((f) => path.relative(process.cwd(), f))
    .join(' ')}`;

module.exports = {
  '*.{js,ts,tsx}': [buildEslintCommand],
  '*.{css,scss}': [buildStyleLintCommand],
};

commitlint.config.js file's content:

module.exports = {
  extends: ['@commitlint/config-conventional'],
};

To verify the code above:

# Enable Git hooks
pnpm husky install

# Add script in package.json to enable git hooks
# when install packages without any arguments
pkg set scripts.prepare="husky install"
# `"prepare": "husky install"` is added in package.json

# πŸ”₯ failed case
git commit -m "test"
# output:
# β§—   input: test
# βœ–   subject may not be empty [subject-empty]
# βœ–   type may not be empty [type-empty]
# βœ–   found 2 problems, 0 warnings

# 🍎 success case
git commit -m "refactor: add pre-commit hook 🌱"

For more information about prepare package hook, you can see in using-npm/scripts.

🍡 Create VS Code setting

Create recommendations VS Code extension:

  1. Ctrl + Shift + P

  2. Type "recom..." and enter

This will create a .vscode/extensions.json file.

extensions.json:

{
    "recommendations": [
        "esbenp.prettier-vscode",
        "dbaeumer.vscode-eslint",
        "stylelint.vscode-stylelint",
        "mintlify.document",
        "streetsidesoftware.code-spell-checker",
        "DavidAnson.vscode-markdownlint",
        "Gruntfuggly.todo-tree",
        "mikestead.dotenv",
        "foxundermoon.next-js",
        "planbcoding.vscode-react-refactor"
    ]
}

πŸ§˜β€β™€οΈ Use Turbopack (alpha)

Turbopack is an incremental bundler optimized for JavaScript and TypeScript, written in Rust, and built into Next.js 13.

On large applications, Turbopack updates 700x faster than Webpack.

To use Turbopack, we edit package.json file:

{
  "scripts": {
    "dev": "next dev --turbo"
  }
}

πŸͺ Client-side data fetching

From NextJS document:

The team behind Next.js has created a React hook library for data fetching called SWR. It is highly recommended if you are fetching data on the client-side. It handles caching, revalidation, focus tracking, refetching on intervals, and more.

And for another reason below:

swr is a React Hooks for Data Fetching.

πŸ‚ Remove unused things

  • Remove unused CSS in the styles folder

  • Remove unused code in the pages/index.tsx folder

πŸƒ Other optional

.markdownlint.yaml for the Markdown file if needed:

# Default state for all rules
default: true

line-length: false

# MD033/no-inline-html - Inline HTML
MD033:
  # Allowed elements
  allowed_elements: ["style"]

🌾 Reference