# Next Formatting

> Use when setting up or fixing lint/format inconsistencies in a Next.js project (package.json has next), when commit messages fail conventional-commit checks, or when configuring eslint-config-next, Prettier, Husky, lint-staged, commitlint, and VSCode/Cursor plugins. Follows https://blog.cp3hnu.com/2025/07/03/nextjs-format/

- Skill: `cp3hnu/next-formatting` (Agent Skill)
- Install (CLI): `npx skillmds@latest add cp3hnu/next-formatting`
- Raw SKILL.md: https://api.skillmd.com/api/skills/cp3hnu/next-formatting/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: cp3hnu (https://skillmd.com/u/cp3hnu)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/cp3hnu/next-formatting

---


# Next.js Code Formatting

For **non-Next React** projects, use the **react-formatting** skill instead.

## Overview

Configure Next.js formatting with:
- ESLint (`eslint-config-next` via FlatCompat) + Prettier
- VSCode/Cursor plugins and settings
- Husky + lint-staged
- commitlint (Conventional Commits)

## Workflow

1. Ensure Next.js project already has ESLint (`create-next-app` with ESLint = Yes)
2. Install and configure Prettier + update ESLint
3. Configure VSCode/Cursor (verify plugins + write settings)
4. Install Husky + lint-staged
5. Add package.json scripts
6. Configure commitlint
7. Verify

## 1) Prerequisites (create-next-app ESLint)

`create-next-app` with ESLint installs `eslint`, `eslint-config-next` and generates `eslint.config.mjs` using FlatCompat:

```js
import { defineConfig, globalIgnores } from "eslint/config";
import nextVitals from "eslint-config-next/core-web-vitals";
import nextTs from "eslint-config-next/typescript";

const eslintConfig = defineConfig([
  ...nextVitals,
  ...nextTs,
  // Override default ignores of eslint-config-next.
  globalIgnores([
    // Default ignores of eslint-config-next:
    ".next/**",
    "out/**",
    "build/**",
    "next-env.d.ts",
  ]),
]);

export default eslintConfig;
```

Do **not** replace this with direct `eslint-config-next/core-web-vitals` imports unless the project already uses that pattern. Prefer FlatCompat as in the guide.

## 2) Install Prettier

```bash
npm install -D prettier eslint-config-prettier eslint-plugin-prettier prettier-plugin-tailwindcss
```

### Prettier config

Create `prettier.config.mjs`:

```js
const config = {
  singleQuote: false,
  tabWidth: 2,
  trailingComma: "all",
  printWidth: 120,
  semi: true,
  arrowParens: "avoid",
  bracketSameLine: true,
  plugins: ["prettier-plugin-tailwindcss"],
};

export default config;
```

Create `.prettierignore` for generated artifacts (`node_modules`, `.next`, `out`, `build`, etc.).

### Update ESLint config

Replace / extend `eslint.config.mjs` to:

```js
import { defineConfig, globalIgnores } from "eslint/config";
import nextVitals from "eslint-config-next/core-web-vitals";
import nextTs from "eslint-config-next/typescript";
import eslintConfigPrettier from "eslint-config-prettier/flat";
import eslintPluginPrettierRecommended from "eslint-plugin-prettier/recommended";

const eslintConfig = defineConfig([
  ...nextVitals,
  ...nextTs,
  globalIgnores([
    ".next/**",
    "out/**",
    "build/**",
    "next-env.d.ts"
  ]),
  eslintConfigPrettier,
  eslintPluginPrettierRecommended,
]);

export default eslintConfig;
```

- `eslint-config-prettier` — disables ESLint rules that conflict with Prettier
- `eslint-plugin-prettier/recommended` — runs Prettier as an ESLint rule (enables `eslint --fix` / `next lint --fix` to format)

## 3) VSCode / Cursor Settings (Required)

### Required extensions

- `esbenp.prettier-vscode` — Prettier - Code formatter
- `dbaeumer.vscode-eslint` — ESLint
- `rvest.vs-code-prettier-eslint` — Prettier ESLint

Check with:

```bash
cursor --list-extensions
# or
code --list-extensions
```

If any are missing, tell the user the exact extension IDs and ask them to install.

### Workspace settings

Create / update `.vscode/settings.json`:

```json
{
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "[javascript]": {
    "editor.defaultFormatter": "rvest.vs-code-prettier-eslint"
  },
  "[typescript]": {
    "editor.defaultFormatter": "rvest.vs-code-prettier-eslint"
  },
  "[javascriptreact]": {
    "editor.defaultFormatter": "rvest.vs-code-prettier-eslint"
  },
  "[typescriptreact]": {
    "editor.defaultFormatter": "rvest.vs-code-prettier-eslint"
  },
  "editor.formatOnSave": true,
  "eslint.validate": ["javascript", "typescript", "javascriptreact", "typescriptreact"],
  "files.associations": {
    "*.css": "tailwindcss"
  }
}
```

## 4) Husky + lint-staged

```bash
npm install -D husky lint-staged
npx husky init
```

### lint-staged.config.mjs

```js
const lintStagedConfig = {
  "*.{js,jsx,ts,tsx}": ["npm run lint-fix"],
  "*.{json,css,md}": ["npm run format-fix"],
};

export default lintStagedConfig;
```

### .husky/pre-commit

```sh
#!/usr/bin/env sh
npx lint-staged
```

## 5) package.json scripts

```json
{
  "scripts": {
    "lint": "next lint --ext .js,.ts,.jsx,.tsx",
    "lint-fix": "next lint --fix --ext .js,.ts,.jsx,.tsx",
    "format": "prettier --check .",
    "format-fix": "prettier --write .",
    "prepare": "husky"
  }
}
```

> If `next lint` is unavailable (removed in some newer Next.js versions), fall back to `eslint .` / `eslint . --fix` while keeping the same Prettier + FlatCompat ESLint config.

## 6) Commitlint

```bash
npm install -D @commitlint/cli @commitlint/config-conventional
```

`commitlint.config.mjs`:

```js
/** @type {import("@commitlint/types").UserConfig} */
const config = {
  extends: ["@commitlint/config-conventional"],
};

export default config;
```

`.husky/commit-msg`:

```sh
#!/usr/bin/env sh
npx --no -- commitlint --edit "$1"
```

Optional script: `"commitlint": "commitlint --edit"`.

Hooks: **`pre-commit`** → lint-staged; **`commit-msg`** → commitlint.

## 7) Optional: eslint-plugin-simple-import-sort

```bash
npm install -D eslint-plugin-simple-import-sort
```

Add to `eslint.config.mjs` array:

```js
import simpleImportSort from "eslint-plugin-simple-import-sort";

// inside the exported array:
{
  name: "import-sort",
  plugins: {
    "simple-import-sort": simpleImportSort,
  },
  rules: {
    "simple-import-sort/imports": "error",
    "simple-import-sort/exports": "error",
  },
}
```

To remove blank lines between import groups:

```js
"simple-import-sort/imports": [
  "error",
  {
    groups: [["^\\u0000", "^node:", "^@?\\w", "^", "^\\."]],
  },
]
```

## 8) Verification Before Completion

```bash
npm run lint
npm run lint-fix
npm run format
npm run format-fix
```

Test hooks (run `npm run prepare` first if hooks do not fire):

```bash
echo "bad commit message" | npx commitlint
echo "chore: verify commitlint" | npx commitlint
```

Stage a file and commit to confirm **pre-commit** runs lint-staged; use a valid Conventional Commit message so **commit-msg** passes.

Report what was configured, what was verified, and any unresolved gaps (especially missing editor plugins).

