Project Setup and the Toolchain
The idea in one sentence
Section titled “The idea in one sentence”Getting productive in Next.js is mostly about one command to scaffold, one command to run, and knowing which files the framework treats as conventions rather than plain code.
Scaffold and run
Section titled “Scaffold and run”Create a project and start it with two commands:
npx create-next-app@latest my-appcd my-appnpm run devcreate-next-app asks whether to use the recommended defaults — TypeScript, ESLint, Tailwind CSS, the App Router, and an AGENTS.md file — or to customize. Taking the defaults is the fastest path and matches everything in this course.
The scripts in package.json are the ones you will run daily:
{ "scripts": { "dev": "next dev", "build": "next build", "start": "next start" }}next dev runs the local development server with hot reloading; next build produces the optimized production output; next start serves that build.
Turbopack is the default bundler
Section titled “Turbopack is the default bundler”As of Next.js 16, Turbopack is the default bundler for both next dev and next build — you do not opt in, it is simply what runs. It is Next.js’s Rust-based successor to webpack, built for fast cold starts and incremental updates.
You configure it, when you need to, through a top-level turbopack key in the config file:
import type { NextConfig } from 'next';
const nextConfig: NextConfig = { turbopack: { // Turbopack-specific options go here },};
export default nextConfig;TypeScript and reading the conventions
Section titled “TypeScript and reading the conventions”Next.js has first-class TypeScript support: the config file itself is next.config.ts, typed with NextConfig, and the framework generates a next-env.d.ts you should not edit. TypeScript is on by default with the recommended setup.
The habit that makes Next.js click is learning to read its conventions. A handful of names are reserved and meaningful anywhere under app/:
- Filenames like
page,layout,loading,error, androuteare behavior, not arbitrary modules. - A folder is a route segment; wrapping a folder name in brackets, as in
[slug], makes it dynamic. next.config.tsis the one place to change framework-level behavior.
Once you can look at a tree and predict the URLs and where code runs, the framework stops feeling magical:
my-app/ app/ layout.tsx page.tsx public/ # static assets served as-is next.config.ts # framework configuration tsconfig.json # TypeScript configurationESLint or Biome — the linter prompt
Section titled “ESLint or Biome — the linter prompt”create-next-app’s customize flow asks a linter question directly: “Which linter would you like to use? ESLint / Biome / None.” ESLint is the traditional choice, with Next.js-specific rules from @next/eslint-plugin-next; Biome is a fast, single-binary linter+formatter with built-in Next.js/React support; None skips linting entirely and can be added later. As of Next.js 16, next lint and the config file’s eslint option are both removed — ESLint is wired up by installing eslint + eslint-config-next and writing a flat config (eslint.config.mjs) yourself, run through the plain ESLint CLI:
import { defineConfig, globalIgnores } from 'eslint/config'import nextVitals from 'eslint-config-next/core-web-vitals'
const eslintConfig = defineConfig([ ...nextVitals, globalIgnores(['.next/**', 'out/**', 'build/**', 'next-env.d.ts']),])
export default eslintConfignpx eslint .React Compiler
Section titled “React Compiler”The React Compiler (reactCompiler: true in next.config.ts) automatically memoizes components, removing most hand-written useMemo/useCallback. In Next.js 16.3 it is not enabled on its own — it runs through a Babel plugin that Next.js does not bundle for you:
// next.config.ts — variant reactCompilerimport type { NextConfig } from 'next';
const nextConfig: NextConfig = { reactCompiler: true,};
export default nextConfig;Turning this on without the plugin installed produces a real build failure, proven in the probe:
Error: Turbopack build failed with 1 error:./next.config.tsError: Failed to resolve package babel-plugin-react-compiler while attempting to resolve React CompilerReact compiler is enabled in next.config.ts. We attempted to resolve React Compiler relative to the next package. Is babel-plugin-react-compiler installed in your node_modules directory?Installing the missing dependency is the whole fix — npm install -D babel-plugin-react-compiler, then the exact same next.config.ts builds cleanly. create-next-app also has a direct --react-compiler flag (and a customize-flow prompt) that sets this up for you, plugin included.
Typed routes and next typegen
Section titled “Typed routes and next typegen”typedRoutes: true makes <Link href> (and next/navigation’s push/replace/prefetch) type-checked against your app’s actual routes — a literal string that isn’t a real route is a type error, not a 404 discovered at runtime:
// next.config.ts — variant typedRoutesimport type { NextConfig } from 'next';
const nextConfig: NextConfig = { typedRoutes: true,};
export default nextConfig;Route types used to only get generated as a side effect of next dev/next build. The dedicated next typegen command (stable since v15.5) generates them on their own, which matters for CI type-checking without a full build:
npx next typegen && npx tsc --noEmitProven in the probe with a route that doesn’t exist:
import Link from 'next/link'
export default function Home() { return ( <div> <Link href="/typed-a">Good link</Link> <Link href="/nope-not-a-route">Bad link</Link> </div> )}app/page.tsx(7,13): error TS2322: Type '"/nope-not-a-route"' is not assignable to type 'UrlObject | RouteImpl<"/nope-not-a-route">'.next typegen printed ✓ Types generated successfully, and only then did tsc --noEmit have the generated route types available to catch the typo.
The AGENTS.md / CLAUDE.md scaffold
Section titled “The AGENTS.md / CLAUDE.md scaffold”The recommended defaults (and the standalone --agents-md flag) also write an AGENTS.md at the project root, plus a CLAUDE.md that just re-exports it (@AGENTS.md) so Claude Code picks up the same rules. Its purpose is blunt: your training data is stale the moment a new major version ships.
<!-- BEGIN:nextjs-agent-rules -->
# This is NOT the Next.js you know
This version has breaking changes — APIs, conventions, and file structure mayall differ from your training data. Read the relevant guide in`node_modules/next/dist/docs/` before writing any code. Heed deprecationnotices.
This block is written and re-added by `next dev` — removing it from a diffonly re-creates the uncommitted change; committing it with your work keepsthe tree clean.
<!-- END:nextjs-agent-rules -->That last point matters for an agent working in this exact repository: next dev regenerates this block on its own, so deleting it is not a durable edit — it comes back.
flowchart LR Create[create-next-app] --> Tree[app/ structure] Tree --> Dev[next dev] Dev --> Turbo[Turbopack bundles] Turbo --> Browser[Live app in the browser] Config[next.config.ts] --> Turbo
Mental Model
Section titled “Mental Model”create-next-app is not “starting from a blank folder” — it is choosing among a fixed set of documented toggles (TypeScript, linter, Tailwind, src/, import alias, React Compiler, AGENTS.md), and every one of those toggles has a real, checkable effect on the generated tree. Treat the whole toolchain the same way: a config key like reactCompiler or typedRoutes is not “magic that works or doesn’t” — it is a documented feature with its own dependency and its own real error message when that dependency is missing.
Trade-offs
Section titled “Trade-offs”| What you get | Benefit | Cost |
|---|---|---|
create-next-app recommended defaults | Correctly wired TypeScript/ESLint/App Router in one command | Opinionated — customizing later means undoing specific defaults |
| ESLint (flat config) | Mature ecosystem, most Next.js examples assume it | You write and own eslint.config.mjs yourself since next lint is gone |
| Biome | One fast binary instead of ESLint+Prettier | Smaller plugin ecosystem than ESLint |
| React Compiler | Fewer manual useMemo/useCallback | Extra dependency (babel-plugin-react-compiler) and a small build-time cost |
typedRoutes | Broken links become type errors, not 404s | Requires next typegen (or dev/build) to regenerate types before tsc sees them |
Common Misconceptions
Section titled “Common Misconceptions”- “
reactCompiler: truejust works once you add the config key.” It requiresbabel-plugin-react-compilerto be installed — without it,next buildfails immediately with a real, named error, not a silent no-op. - “
next lintstill exists in Next.js 16.” It was removed in v16.0.0, along with theeslintnext.config.jsoption — ESLint is now run through the plain ESLint CLI against your own flat config. - “
tsc --noEmitalone catches every typed-route mistake.” Route types come from.next/types, generated bynext dev,next build, ornext typegen— running baretscin a fresh checkout (or CI) without one of those first means the route types don’t exist yet. - “The
AGENTS.mdblock is just a suggestion I can delete.”next devre-adds it on its own; deleting it from a diff without addressing why only recreates an uncommitted change on the nextdevrun.
Reading AI-generated code
Section titled “Reading AI-generated code”When a generated snippet touches next.config.ts or the project scaffold, check for these before trusting it:
- Does it set
reactCompiler: truewithout any mention of installingbabel-plugin-react-compiler? The build will fail with a clear “Failed to resolve package” error the first time it runs. - Does it use
next lintor aneslintkey insidenext.config.js? Both were removed in Next.js 16 — current code uses the ESLint CLI against a flateslint.config.mjs. - Does it assume
<Link href="...">type-checks against real routes withouttypedRoutes: truein config, and without anext typegen/dev/buildrun to produce the types? - Does it reference
getInitialProps,next.config.jswithmodule.exports, or other patterns that predate the current typed, ESM-friendlynext.config.ts?
🐛 Spot the bug
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {reactCompiler: true,typedRoutes: true,}
export default nextConfigShow the answer
This config is only half the story — reactCompiler: true requires babel-plugin-react-compiler to actually be installed as a dependency, and typedRoutes: true requires a next typegen (or dev/build) run before tsc can see the generated route types. Proven in the probe: with this exact config and no babel-plugin-react-compiler in node_modules, next build fails immediately with Error: Failed to resolve package babel-plugin-react-compiler while attempting to resolve React Compiler. The config key alone changes nothing — the dependency (and, for typed routes, a type-generation step) has to exist too.