Skip to content

Project Setup and the Toolchain

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.

Create a project and start it with two commands:

Terminal window
npx create-next-app@latest my-app
cd my-app
npm run dev

create-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.

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:

next.config.ts
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
turbopack: {
// Turbopack-specific options go here
},
};
export default nextConfig;

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, and route are behavior, not arbitrary modules.
  • A folder is a route segment; wrapping a folder name in brackets, as in [slug], makes it dynamic.
  • next.config.ts is 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 configuration

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:

eslint.config.mjs
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 eslintConfig
Terminal window
npx eslint .

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 reactCompiler
import 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.ts
Error: Failed to resolve package babel-plugin-react-compiler while attempting to resolve React Compiler
React 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.

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 typedRoutes
import 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:

Terminal window
npx next typegen && npx tsc --noEmit

Proven in the probe with a route that doesn’t exist:

app/page.tsx
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 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 may
all differ from your training data. Read the relevant guide in
`node_modules/next/dist/docs/` before writing any code. Heed deprecation
notices.
This block is written and re-added by `next dev` — removing it from a diff
only re-creates the uncommitted change; committing it with your work keeps
the 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
From scaffold to a running dev server

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.

What you getBenefitCost
create-next-app recommended defaultsCorrectly wired TypeScript/ESLint/App Router in one commandOpinionated — customizing later means undoing specific defaults
ESLint (flat config)Mature ecosystem, most Next.js examples assume itYou write and own eslint.config.mjs yourself since next lint is gone
BiomeOne fast binary instead of ESLint+PrettierSmaller plugin ecosystem than ESLint
React CompilerFewer manual useMemo/useCallbackExtra dependency (babel-plugin-react-compiler) and a small build-time cost
typedRoutesBroken links become type errors, not 404sRequires next typegen (or dev/build) to regenerate types before tsc sees them
  • “reactCompiler: true just works once you add the config key.” It requires babel-plugin-react-compiler to be installed — without it, next build fails immediately with a real, named error, not a silent no-op.
  • “next lint still exists in Next.js 16.” It was removed in v16.0.0, along with the eslint next.config.js option — ESLint is now run through the plain ESLint CLI against your own flat config.
  • “tsc --noEmit alone catches every typed-route mistake.” Route types come from .next/types, generated by next dev, next build, or next typegen — running bare tsc in a fresh checkout (or CI) without one of those first means the route types don’t exist yet.
  • “The AGENTS.md block is just a suggestion I can delete.” next dev re-adds it on its own; deleting it from a diff without addressing why only recreates an uncommitted change on the next dev run.

When a generated snippet touches next.config.ts or the project scaffold, check for these before trusting it:

  • Does it set reactCompiler: true without any mention of installing babel-plugin-react-compiler? The build will fail with a clear “Failed to resolve package” error the first time it runs.
  • Does it use next lint or an eslint key inside next.config.js? Both were removed in Next.js 16 — current code uses the ESLint CLI against a flat eslint.config.mjs.
  • Does it assume <Link href="..."> type-checks against real routes without typedRoutes: true in config, and without a next typegen/dev/build run to produce the types?
  • Does it reference getInitialProps, next.config.js with module.exports, or other patterns that predate the current typed, ESM-friendly next.config.ts?

🐛 Spot the bug

next.config.ts
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
reactCompiler: true,
typedRoutes: true,
}
export default nextConfig
Show 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.

Which command scaffolds a new Next.js project?
What is the default bundler in current Next.js for dev and build?
What is the TypeScript config file for the framework called?
What does wrapping a folder name in brackets, like [slug], do?
What happens when this builds without babel-plugin-react-compiler installed?
// next.config.ts
const nextConfig: NextConfig = {
  reactCompiler: true,
}
Since Next.js 16, how do you run ESLint in a project scaffolded with the ESLint option?
typedRoutes: true is set, but a CI job runs a fresh checkout straight through tsc --noEmit with no next dev, next build, or next typegen step first. What happens to a <Link href="/typo"> where /typo is not a real route?