Answering the create-next-app Prompts Without Regretting It Later
Your team starts a storefront on Monday and the first command anyone types is npx create-next-app@latest. It finishes in about thirty seconds and asks maybe six questions — and those answers quietly decide your language, your router, your bundler, and your styling pipeline for the next year. None of them are fatal, but correcting one later means hand-editing tsconfig.json, deleting generated files, and justifying a diff nobody asked for. Treat the prompts as configuration, not small talk.
On a laptop the interactive run looks like this (Next.js 15):
What is your project named? storefront
Would you like to use TypeScript? … No / Yes
Which linter would you like to use? … ESLint / None
Would you like to use Tailwind CSS? … No / Yes
Would you like your code inside a `src/` directory? … No / Yes
Would you like to use App Router? (recommended) … No / Yes
Would you like to use Turbopack for `next dev`? … No / Yes
Would you like to customize the import alias (`@/*` by default)? … Yes
For CI images, devcontainers, and onboarding scripts you skip the prompts entirely — same decisions, all as flags:
npx create-next-app@latest storefront \
--ts --tailwind --eslint --app \
--import-alias "@/*" --use-npm
The flags worth memorizing:
| Flag | What it changes | Why you care |
|---|---|---|
--ts / --typescript (default) vs --js |
Language, tsconfig.json, .ts(x) files |
TypeScript is the default since Next 14; --js is the opt-out |
--app (default) vs --no-app |
Creates app/ vs the legacy pages/ directory |
Sets the file convention routes are built from |
--tailwind |
PostCSS pipeline, globals.css entry |
Styling is decided here, not later |
--eslint |
Flat config in eslint.config.mjs |
Runs inside next build, so lint errors can fail deploys |
--src-dir |
Moves app/ under src/ |
Keeps route files out of the repo root |
--turbopack (15.3+) |
Dev script becomes next dev --turbopack |
Faster HMR; skip it if a webpack-only plugin is required |
--import-alias |
Writes paths into tsconfig.json |
@/* is what import Button from '@/components/Button' resolves against |
--skip-install |
Creates files without installing | Useful when a lockfile or proxy must be handled separately |
--example <name> |
Scaffolds from an official example instead of the default template | A shortcut when you want a known starting shape |
Two choices deserve a second look.
App Router. Answering yes creates app/, the directory convention that every later route depends on — the mechanics of that mapping are App Directory Conventions. Answering no gives you pages/, the Pages Router, which still builds but receives no new features; Server Actions, Partial Prerendering, and Cache Components are App Router territory. A recruiter asking "do you know the App Router?" is really asking whether you have built since 2023.
React 19. Next 15 pairs with React 19 (Next 15.1 onward installs the stable react@19, after 15.0 tracked the release candidate). That breaks libraries that pin react@^18:
npm ERR! ERESOLVE: Unable to resolve dependency tree
npm ERR! While resolving: some-datagrid@3.2.1
npm ERR! Found: react@19.0.0
npm ERR! peer react@"^18.2.0" from some-datagrid@3.2.1
Pick a release that declares React 19 support, or install with --legacy-peer-deps and treat the warning as debt. Silently forcing it is how you end up with a grid component that throws Invalid hook call in production.
Styling has the same version trap. Recent create-next-app releases scaffold Tailwind v4, so globals.css begins with @import "tailwindcss"; and there is no tailwind.config.* at all. Older 15.0-era scaffolds used Tailwind v3 with @tailwind base; @tailwind components; @tailwind utilities;. Copy that v3 header into a v4 project and every utility class silently disappears — no error, just an unstyled page — so check which generation your scaffold belongs to before you copy snippets from a tutorial.
Reading the Scaffold: What Each Generated File Is Responsible For
Once the CLI exits you have a working repository. Knowing which file is responsible for which job keeps you from "cleaning up" something the framework depends on. This is the default shape for a TypeScript + Tailwind App Router project without src/:
storefront/
├── app/
│ ├── favicon.ico
│ ├── globals.css # Tailwind entry point, imported by layout
│ ├── layout.tsx # root layout — must render <html> and <body>
│ └── page.tsx # the component rendered at /
├── public/ # static files served verbatim at /
│ ├── next.svg
│ └── vercel.svg
├── eslint.config.mjs # flat ESLint config extending eslint-config-next
├── next.config.ts # build/runtime configuration (or .mjs / .js)
├── package.json
├── postcss.config.mjs # wires Tailwind into the CSS pipeline
├── README.md
├── tsconfig.json # paths: { "@/*": ["./*"] }
└── .gitignore # node_modules/, .next/, local env files
With --src-dir, app/ moves to src/app/ while public/, config files, and package.json stay at the root — the router only cares that an app/ directory exists somewhere. Skip --src-dir in a monorepo and route files end up scattered next to turbo.json and workspace configs.
Read package.json scripts before anything else:
| Script | Runs | Used for |
|---|---|---|
npm run dev |
next dev (optionally --turbopack) |
Local development on port 3000 |
npm run build |
next build |
Production bundle, type check, lint, prerender |
npm run start |
next start |
Serving .next/ in production |
npm run lint |
next lint |
The same ESLint config the build will enforce |
Three details the scaffold hands you for free:
The import alias is real configuration, not decoration. tsconfig.json contains "paths": { "@/*": ["./*"] }, which is the only reason @/components/Button resolves. Change the alias in the CLI prompt but not in tsconfig.json (or vice versa) and your editor marks every import as an error while the bundler may still resolve — a mismatch worth checking when imports fail in CI but work locally.
Linting is wired in from commit zero. eslint.config.mjs uses the modern flat-config format with eslint-config-next, including rules such as @next/next/no-img-element. Older tutorials show .eslintrc.json; that file does not exist in a fresh Next 15 scaffold, and next lint will happily tell you there is nothing to fix if you only created the legacy file. Because next build runs ESLint, a new warning-level regression can fail a pipeline that never failed before.
Git is already initialized. create-next-app runs git init for you, so your first commit is one command away:
git add -A && git commit -m "chore: scaffold storefront with create-next-app"
git status --short # confirm only source files are tracked
The generated .gitignore excludes node_modules/, .next/, build output, and local environment files. Verify rather than trust: if git status --short shows .env.local or .next/, stop and fix the ignore file before the first commit, because history is much harder to clean than the index.
Two files are generated after the CLI finishes. Running npm run dev or npm run build creates next-env.d.ts, which wires Next's types (including next/image and route props) into the editor; it sits in .gitignore for a reason — editing it by hand is overwritten on the next run. The .next/ directory is the other one: dev cache, compiled output, prerendered HTML. It is regenerated from source, never committed, and deleting it is always a safe way to clear a stale-build mystery.
Equally important is what the scaffold does not generate. There is no components/, no lib/, no test setup, and no data layer — nothing that would create an accidental route, and nothing to reuse either. Those are conventions you add deliberately, and App Directory Conventions is where the map of routable vs. non-routable files is drawn.
Proving the Scaffold Works: dev, build, and the Three First-Run Failures
A scaffold that has not been started is an assumption. Prove it twice: once in development, once as a production build.
npm run dev
▲ Next.js 15.4.1 (Turbopack)
- Local: http://localhost:3000
✓ Ready in 412ms
Then npm run build, which does the heavy lifting: TypeScript type check, ESLint pass, and prerendering of every route it can.
Route (app) Size First Load JS
┌ ○ / 978 B 92.1 kB
├ ○ /about 155 B 91.3 kB
└ ƒ /api/health 0 B 0 B
+ 42 function routes
○ (Static) prerendered as static HTML
ƒ (Dynamic) server-rendered on demand
If both commands pass, the project is genuinely usable. Here are the three failures that waste the most time on day one.
1. The port is already taken. A forgotten dev server from yesterday answers before yours can bind:
Error: listen EADDRINUSE: address already in use :::3000
Either move the server with npm run dev -- -p 3001, or kill the occupant: lsof -ti:3000 | xargs kill -9 on macOS/Linux, and on Windows netstat -ano | findstr :3000 followed by taskkill /PID <pid> /F. Do not assume it is always your own process — a teammate's demo running on the same machine is the usual culprit.
2. Node is too old. Next 15 requires Node.js 18.18 or newer. npm surfaces it as an engine warning rather than a hard failure, which is why the install "succeeds" and the dev server then crashes:
npm WARN EBADENGINE Unsupported engine {
npm WARN EBADENGINE package: 'next@15.4.1',
npm WARN EBADENGINE required: { node: '>=18.18.0' },
Check with node -v, fix with nvm install 20 (or fnm, or Volta), then delete node_modules and reinstall — a partially installed tree under the wrong ABI is not repaired by upgrading Node after the fact.
3. A dependency pins React 18. Covered in the first chapter: ERESOLVE: Unable to resolve dependency tree. The fix is a React 19-compatible release, not a shrug.
Once the basics pass, read the build output as information rather than decoration. The ○ marks are prerendered HTML — they answer with zero server work. The ƒ marks are rendered per request. A brand-new scaffold showing ƒ / tells you something in app/page.tsx is already opting into dynamic rendering (a cookie read, a searchParams access, a no-store fetch); a fresh project with no data fetching should be almost entirely green. Type errors also stop the build (Type error: Property 'slug' does not exist on type ...), and by default ESLint errors do too — a reminder that npm run build is the real gate your CI will run, which you can tune later in next.config Options.
Finish with the third command in that table, npm run start, which serves the build you just produced on port 3000 with no compilation step. It is a different runtime: no hot reload, no source maps for your edits, and environment variables read exactly as the build left them. Running it locally for sixty seconds catches a class of bugs — an asset path that only breaks outside dev, a server variable missing from your shell — that next dev will happily hide.
Two minutes of verification buys a known-good baseline: when a later feature breaks something, you can diff against a build that already worked instead of arguing about the scaffold. In an interview, "how do you bootstrap a Next.js project?" is answered well by naming the command, choosing two flags and justifying them (App Router for convention, TypeScript for the compiler as a safety net), and then describing the dev + build check — the verification step is the part most candidates leave out.
Quiz
1. You run `npx create-next-app@latest shop --ts --tailwind --eslint --app --src-dir`. Which statement about the result is true?
2. `npm run dev` prints `Error: listen EADDRINUSE: address already in use :::3000`. What is the correct fix?
3. What must be true for `import Button from '@/components/Button'` to type-check without relative paths?
4. npm prints `npm WARN EBADENGINE ... required: { node: '>=18.18.0' }` right after scaffolding. What does it mean?
Flashcards
Question
Which create-next-app decisions are expensive to reverse after day one?
Click to reveal answer
Answer
Language (TypeScript vs JS), router (app/ vs pages/), styling pipeline (Tailwind generation), src/ layout, the import alias baked into tsconfig paths, and the dev bundler — reversing any of them means editing generated config and moving files by hand rather than flipping a flag.
Question
What does --src-dir change, and what does it leave at the project root?
Click to reveal answer
Answer
It moves the route folder to src/app/. public/, package.json, next.config, tsconfig.json, and the ESLint config stay at the root — the router only requires that an app/ directory exists somewhere in the project.
Question
Which command proves a scaffold works, and what does its output tell you?
Click to reveal answer
Answer
`npm run build` — it type-checks, runs ESLint, prerenders what it can, and prints a route table where ○ marks statically prerendered routes and ƒ marks routes rendered on demand per request.
Question
What are the three classic first-run failures of a fresh Next 15 project?
Click to reveal answer
Answer
EADDRINUSE on port 3000 (another dev server), EBADENGINE when Node is below 18.18, and ERESOLVE when a library pins react@^18 while Next 15 installs React 19 — each fails at a different layer: OS, runtime, package manager.
Question
What does create-next-app leave in the repository before your first commit?
Click to reveal answer
Answer
A git repo with no commits (it runs git init), a .gitignore covering node_modules/, .next/ and local env files, lint and build scripts in package.json, tsconfig paths, and no components/lib/test folders — those are conventions you add deliberately.
Revision Notes
Key Takeaways
- 1. create-next-app is configuration, not boilerplate: --app, --ts, --src-dir, --import-alias and the Tailwind generation decide file layout and tooling for the life of the repo
- 2. Next 15 pairs with React 19 and recent scaffolds use Tailwind v4, so old tutorials produce ERESOLVE peer conflicts and v3 directives that silently render an unstyled page
- 3. The scaffold ships git init with no commit, a .gitignore, flat-config ESLint (eslint.config.mjs) and tsconfig paths — check `git status --short` before your first commit
- 4. Verify twice: `npm run dev` for the ready banner on port 3000, then `npm run build` for type check, lint, and the ○/ƒ route table
- 5. Read build output as data — a fresh scaffold should be almost entirely ○, so an early ƒ tells you something in the page is already opting into dynamic rendering
Interview Tips
- • "How do you bootstrap a Next.js project?" — give the non-interactive command, name two flags with reasons (App Router for conventions, TypeScript for the compiler as a safety net), then describe the dev + build check; the verification step is what most candidates omit
- • "App Router or Pages Router for a new project?" — App Router: folders map to URLs, layouts persist across navigation, components render on the server by default, and new features land there first; the Pages Router still builds but receives no new capability
- • "A teammate's dev server won't start — walk me through it" — test in layers: port in use (EADDRINUSE), Node version for Next 15 (18.18+), then package resolution (ERESOLVE against React 19) before you suggest reinstalling anything
- • "What is the difference between next dev and next start?" — dev compiles on demand with hot reload and development diagnostics; start serves an existing `next build` exactly as CI does, which is why asset and environment bugs surface only there
Cheat Sheet
Scaffolding with create-next-app
Reproducible scaffold (no prompts):
npx create-next-app@latest storefront \
--ts --tailwind --eslint --app \
--import-alias "@/*" --use-npm
Flags that matter:
| Flag | Effect |
|---|---|
--app / --no-app |
App Router (app/) vs legacy Pages Router (pages/) |
--ts / --js |
TypeScript default; --js opts out |
--src-dir |
Route folder becomes src/app/ |
--turbopack (15.3+) |
next dev --turbopack in the dev script |
--skip-install |
Files only; install yourself later |
--example <name> |
Scaffold from an official example |
Generated by default: app/layout.tsx + app/page.tsx, public/, tsconfig.json (@/* paths), eslint.config.mjs (flat config), next.config.ts, postcss.config.mjs, .gitignore, and a git init with no commit yet.
Verify the scaffold:
npm run dev # ready banner on http://localhost:3000
npm run build # type check + lint + prerender → route table
Day-one failures:
| Symptom | Cause | Fix |
|---|---|---|
listen EADDRINUSE :::3000 |
another dev server holds the port | npm run dev -- -p 3001 or kill the PID |
npm WARN EBADENGINE ... >=18.18.0 |
Node older than 18.18 | upgrade Node, wipe node_modules, reinstall |
ERESOLVE ... peer react@"^18.2.0" |
library pins React 18 (Next 15 ships React 19) | use a React 19 release or --legacy-peer-deps |
| page renders unstyled, no error | Tailwind v4 header (@import "tailwindcss") mixed with v3 directives |
keep one Tailwind generation's setup |
Build output legend: ○ static (prerendered) · ƒ dynamic (server-rendered per request). A fresh scaffold should be nearly all ○.
Continue Learning
Before You Start
- No prerequisites listed.