Next.js Code Formatting
For non-Next React projects, use the react-formatting skill instead.
Overview
Configure Next.js formatting with:
- ESLint (
eslint-config-nextvia FlatCompat) + Prettier - VSCode/Cursor plugins and settings
- Husky + lint-staged
- commitlint (Conventional Commits)
Workflow
- Ensure Next.js project already has ESLint (
create-next-appwith ESLint = Yes) - Install and configure Prettier + update ESLint
- Configure VSCode/Cursor (verify plugins + write settings)
- Install Husky + lint-staged
- Add package.json scripts
- Configure commitlint
- Verify
1) Prerequisites (create-next-app ESLint)
create-next-app with ESLint installs eslint, eslint-config-next and generates eslint.config.mjs using FlatCompat:
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
npm install -D prettier eslint-config-prettier eslint-plugin-prettier prettier-plugin-tailwindcss
Prettier config
Create prettier.config.mjs:
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:
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 Prettiereslint-plugin-prettier/recommended— runs Prettier as an ESLint rule (enableseslint --fix/next lint --fixto format)
3) VSCode / Cursor Settings (Required)
Required extensions
esbenp.prettier-vscode— Prettier - Code formatterdbaeumer.vscode-eslint— ESLintrvest.vs-code-prettier-eslint— Prettier ESLint
Check with:
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:
{
"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
npm install -D husky lint-staged
npx husky init
lint-staged.config.mjs
const lintStagedConfig = {
"*.{js,jsx,ts,tsx}": ["npm run lint-fix"],
"*.{json,css,md}": ["npm run format-fix"],
};
export default lintStagedConfig;
.husky/pre-commit
#!/usr/bin/env sh
npx lint-staged
5) package.json scripts
{
"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 lintis unavailable (removed in some newer Next.js versions), fall back toeslint ./eslint . --fixwhile keeping the same Prettier + FlatCompat ESLint config.
6) Commitlint
npm install -D @commitlint/cli @commitlint/config-conventional
commitlint.config.mjs:
/** @type {import("@commitlint/types").UserConfig} */
const config = {
extends: ["@commitlint/config-conventional"],
};
export default config;
.husky/commit-msg:
#!/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
npm install -D eslint-plugin-simple-import-sort
Add to eslint.config.mjs array:
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:
"simple-import-sort/imports": [
"error",
{
groups: [["^\\u0000", "^node:", "^@?\\w", "^", "^\\."]],
},
]
8) Verification Before Completion
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):
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).