BackDev Patterns & Practices
Setup//

Next js Development Setup

Next.js 14 setup: app-dir conventions, TypeScript, ESLint and Prettier configuration, dev vs production builds, and deployment best practices.

Introduction

This guide covers the complete setup for Next.js development including project creation (App Router), ESLint, Prettier, Husky + lint-staged, and TypeScript support. Use this as a reference when bootstrapping new applications or standardizing team tooling.

TL;DR

Bootstrap with the App Router + TypeScript, enable ESLint and Prettier, add Husky + lint-staged for pre-commit checks, and use `pnpm` for faster installs. CI should run `tsc --noEmit`, `eslint` and `prettier --check`.

Prerequisites

Before starting, make sure you have:

  • Node.js (v18.17 or higher required)
  • npm, yarn, or pnpm(package manager)
  • A code editor (VSCode recommended)

Check Versions

1node --version  # Should be v18.17 or higher
2npm --version   # Should be 9.x or higher

Step 1 — Create Next.js Project

Create Project with create-next-app

1npx create-next-app@latest my-next-app

Configuration Prompts

You will be asked several questions (recommendations in parentheses):

  • TypeScript: Yes
  • ESLint: Yes
  • Tailwind CSS: Yes (recommended)
  • src/ directory: Yes (recommended)
  • App Router: Yes (recommended)

Navigate & Test

1cd my-next-app
2
3npm run dev

Open the app at http://localhost:3000.

Project Structure (App Router)

1my-next-app/
2├── public/                   # Static assets
3├── src/
4│   ├── app/
5│   │   ├── layout.tsx        # Root layout
6│   │   ├── page.tsx          # Home page
7│   │   ├── globals.css       # Global styles
8│   │   └── favicon.ico       # Favicon
9├── .eslintrc.json            # ESLint config (auto-generated)
10├── next.config.js            # Next.js config
11├── package.json              # Dependencies
12├── tsconfig.json             # TypeScript config
13└── tailwind.config.ts        # Tailwind config

Step 2 — Understanding Next.js ESLint Configuration

Auto-Generated ESLint Config

Next.js creates a minimal config:

1{
2  "extends": "next/core-web-vitals"
3}

Extend with TypeScript and Custom Rules

1{
2  "extends": [
3    "next/core-web-vitals",
4    "next/typescript"
5  ],
6  "rules": {
7    "@typescript-eslint/no-unused-vars": "warn",
8    "@typescript-eslint/no-explicit-any": "warn",
9    "no-console": "warn",
10    "prefer-const": "error",
11    "react/jsx-curly-brace-presence": ["warn", { "props": "never", "children": "never" }],
12    "react/self-closing-comp": ["warn", { "component": true, "html": true }]
13  }
14}

Next.js Specific ESLint Rules

Examples: @next/next/no-html-link-for-pages,@next/next/no-img-element, and@next/next/no-sync-scripts. Prefer Next.js components (`Link`, `Image`, `Script`) where applicable.

Add ESLint Scripts

1{
2  "scripts": {
3    "dev": "next dev",
4    "build": "next build",
5    "start": "next start",
6    "lint": "next lint",
7    "lint:fix": "next lint --fix"
8  }
9}

Test ESLint

1npm run lint

Step 3 — Configure Prettier for Next.js

Install Prettier

1npm install --save-dev prettier eslint-config-prettier

Create .prettierrc

1{
2  "semi": true,
3  "singleQuote": true,
4  "tabWidth": 2,
5  "useTabs": false,
6  "trailingComma": "es5",
7  "printWidth": 100,
8  "arrowParens": "always",
9  "endOfLine": "lf",
10  "bracketSpacing": true,
11  "bracketSameLine": false,
12  "jsxSingleQuote": false,
13  "plugins": ["prettier-plugin-tailwindcss"]
14}

Install Tailwind Prettier Plugin

1npm install --save-dev prettier-plugin-tailwindcss

Create .prettierignore

1# Dependencies
2node_modules/
3
4# Build outputs
5.next/
6out/
7dist/
8
9# Cache
10.cache/
11.turbo/
12
13# Logs
14*.log
15
16# Environment files
17.env*.local

Integrate Prettier with ESLint

1{
2  "extends": [
3    "next/core-web-vitals",
4    "next/typescript",
5    "prettier"
6  ],
7  "rules": {
8    "@typescript-eslint/no-unused-vars": "warn",
9    "@typescript-eslint/no-explicit-any": "warn",
10    "no-console": "warn",
11    "prefer-const": "error"
12  }
13}

VSCode Settings

1{
2  "editor.defaultFormatter": "esbenp.prettier-vscode",
3  "editor.formatOnSave": true,
4  "editor.codeActionsOnSave": {
5    "source.fixAll.eslint": "explicit",
6    "source.organizeImports": "explicit"
7  },
8  "[typescript]": {
9    "editor.defaultFormatter": "esbenp.prettier-vscode"
10  },
11  "[typescriptreact]": {
12    "editor.defaultFormatter": "esbenp.prettier-vscode"
13  },
14  "[javascript]": {
15    "editor.defaultFormatter": "esbenp.prettier-vscode"
16  },
17  "[javascriptreact]": {
18    "editor.defaultFormatter": "esbenp.prettier-vscode"
19  },
20  "[json]": {
21    "editor.defaultFormatter": "esbenp.prettier-vscode"
22  }
23}

Add Prettier Scripts

1{
2  "scripts": {
3    "dev": "next dev",
4    "build": "next build",
5    "start": "next start",
6    "lint": "next lint",
7    "lint:fix": "next lint --fix",
8    "format": "prettier --write "src/**/*.{ts,tsx,css,json}"",
9    "format:check": "prettier --check "src/**/*.{ts,tsx,css,json}""
10  }
11}

Step 4 — Setup Husky and lint-staged

Install & Initialize

1npm install --save-dev husky lint-staged
2
3npx husky init

Configure lint-staged

1{
2  "lint-staged": {
3    "*.{ts,tsx}": [
4      "eslint --fix --max-warnings=0",
5      "prettier --write"
6    ],
7    "*.{css,json}": [
8      "prettier --write"
9    ]
10  }
11}

Create Pre-Commit Hook

1#!/usr/bin/env sh
2. "$(dirname -- "$0")/_/husky.sh"
3
4npx lint-staged

Update package.json Scripts

1{
2  "scripts": {
3    "dev": "next dev",
4    "build": "next build",
5    "start": "next start",
6    "lint": "next lint",
7    "lint:fix": "next lint --fix",
8    "format": "prettier --write "src/**/*.{ts,tsx,css,json}"",
9    "format:check": "prettier --check "src/**/*.{ts,tsx,css,json}"",
10    "prepare": "husky install"
11  }
12}

Next.js Best Practices

App Router File Conventions

Use `layout.tsx`, `page.tsx`, `loading.tsx`, `error.tsx`, and `not-found.tsx` in your app route folders. Keep server components by default and add use client only when necessary.

Server vs Client Components

1// Server Component (default)
2export default function Page() {
3  // runs on the server
4  return <div>Server Component</div>
5}
6
7// Client Component
8'use client'
9import { useState } from 'react'
10export default function Counter() {
11  const [count, setCount] = useState(0)
12  return <button onClick={() => setCount(count + 1)}>{count}</button>
13}

Image Optimization

1import Image from 'next/image'
2
3<Image src="/hero.jpg" alt="Hero" width={1200} height={600} priority />

Metadata & SEO

1import { Metadata } from 'next'
2
3export const metadata: Metadata = {
4  title: 'Home | My Next.js App',
5  description: 'Welcome to my Next.js application'
6}

Quick verification

1# Check node and package manager
2    node --version
3    pnpm --version
4
5    # Start dev server
6    pnpm dev    # (or npm run dev)
7
8    # Lint and format checks
9    pnpm exec eslint .
10    pnpm exec prettier --check "src/**/*.{ts,tsx,css,json}"

Complete Setup Checklist

  • Node.js v18.17+ installed
  • Project created with `create-next-app`
  • App Router & TypeScript selected
  • ESLint and Prettier configured
  • Husky + lint-staged installed and tested

Useful Commands

1# Start development server
2npm run dev
3
4# Build for production
5npm run build
6
7# Start production server
8npm run start
9
10# Lint
11npm run lint
12
13# Format
14npm run format

Troubleshooting

Common issues and fixes: missing plugins (run `npm install`), image optimization errors (add width/height or configure remote domains in `next.config.js`), and hydration mismatches (move browser-only code to client components).

Resources

About

Dev Patterns & Practices is a space for long-form thinking on design, technology, and craft. Every piece is written with care and the belief that the best ideas deserve room to breathe.