create-next-app Ships TypeScript by Default. Here's Every Line It Writes.
The --typescript flag is a no-op in Next 16. The 24-option tsconfig, the gitignored next-env.d.ts that turns out not to matter here, and where the type check really runs.
npx create-next-app@latest already builds a TypeScript project — the --typescript flag is the default and changes nothing. What the scaffold actually writes is three things: a 24-option tsconfig.json, a generated next-env.d.ts it tells you to gitignore, and route types regenerated on every next dev, next build or next typegen.
This storefront was scaffolded that way and now runs 139 .tsx files and 81 .ts files with zero : any annotations. The tsconfig.json below is still, line for line, the one create-next-app wrote. Here is what each line does, which of them are worth changing, and which generated file turns out to matter far less than its reputation.
The command, and the flag that stopped mattering
npx create-next-app@latest my-app
That is the whole answer for a new project. TypeScript is on. --typescript and --ts still parse, so copying an older tutorial does no harm, but they select a default. The prompts that do change the output are the App Router choice, src/ directory, the @/* import alias, Tailwind, and ESLint.
For an existing JavaScript project, Next.js documents the opposite flow: rename one file to .ts/.tsx, then run next dev or next build. It installs typescript, @types/react and @types/node for you and writes a tsconfig.json with its recommended options — the same file the scaffold would have produced.
Every line of the generated tsconfig.json
This is the unedited scaffold output, still live at the repo root:
{
"compilerOptions": {
"target": "ES2017",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "react-jsx",
"incremental": true,
"plugins": [{ "name": "next" }],
"paths": { "@/*": ["./src/*"] }
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
".next/dev/types/**/*.ts",
"**/*.mts"
],
"exclude": ["node_modules"]
}
| Option | What it is doing | Worth changing? |
|---|---|---|
target: "ES2017" | Syntax level the compiler assumes | Rarely — the bundler, not tsc, decides what ships |
allowJs: true | Lets .js files sit beside .ts | Yes, once the migration is done. This repo has 0 .js files under src/ and the flag is still on |
skipLibCheck: true | Skips type-checking node_modules .d.ts files | Keep it. Turning it off here surfaced 7 errors, all of them inside dependencies |
strict: true | The whole point of the scaffold | Never turn this off |
noEmit: true | tsc checks types and writes nothing | Keep — the bundler emits |
moduleResolution: "bundler" | Resolves imports the way a bundler does | Keep |
isolatedModules: true | Each file must compile alone (forces export type) | Keep — it is what makes per-file transpilation safe |
jsx: "react-jsx" | No import React needed per file | Keep |
incremental: true | Writes .tsbuildinfo to speed repeat checks | Keep, and confirm it is gitignored |
plugins: [{ "name": "next" }] | The editor plugin that flags bad use client and segment config | Keep; in VS Code also pick "Use Workspace Version" |
paths: { "@/*": ["./src/*"] } | The alias prompt's answer | Keep and use it everywhere |
Two entries in include are newer than most tutorials: .next/types/**/*.ts and .next/dev/types/**/*.ts. They pull in files Next.js generates rather than files you wrote, which is the part worth understanding next.
next-env.d.ts: generated, gitignored, and less load-bearing than you think
next-env.d.ts is three lines, and in this project it reads:
/// <reference types="next" />
/// <reference types="next/image-types/global" />
import "./.next/types/routes.d.ts";
Next.js's own guidance is to add it to .gitignore — the create-next-app .gitignore already does, which is why this repo has no next-env.d.ts in version control — while keeping it in the tsconfig.json include array. So a fresh clone is missing a file its tsconfig.json names. Running next dev, next build or next typegen writes it back; next typegen took 0.5s here.
The usual explanation is that without it TypeScript cannot understand non-code imports. That is true for value imports, and it is worth knowing exactly where the line falls, because it is not where most posts put it. Measured on this repo, with no next-env.d.ts present at all:
$ npx tsc --noEmit # 220 .ts/.tsx files, no next-env.d.ts
real 0m5.0s # clean: zero errors
Five seconds, no errors — even though src/app/layout.tsx opens with import "./globals.css". Dropping a file with a deliberately bogus import "./definitely-not-a-real-file.css" into src/ also type-checked clean. The reason is that a side-effect-only import is not resolved for types at all; only a binding import is:
import "./globals.css"; // never type-checked — no error, ever
import logo from "./logo.png"; // needs next-env.d.ts, or TS2307
This codebase takes the second line out of play entirely: it renders images as plain <img src="/..."> against files in public/, so it imports zero images as modules. That is why deleting the generated file changes nothing measurable here — and why on a project that does import hero from "./hero.png", it changes everything. Know which project you have before you treat the file as optional.
The types you did not write
next typegen produced three files under .next/types:
| File | Size | What is in it |
|---|---|---|
routes.d.ts | 103 lines | A union of every route in the app, plus a ParamMap of each route's params |
cache-life.d.ts | 145 lines | The named cache profiles valid in cacheLife() |
validator.ts | — | Checks each page/layout/route file against the shape Next.js expects |
The route union is generated from the file system, so it is exact. In this project it currently resolves to 27 app routes and 4 route handlers:
type AppRoutes = "/" | "/blog" | "/blog/[slug]" | "/changelog" | "/contact" |
"/dashboard" | "/dashboard/settings" | "/docs" | /* … */ | "/terms"
type AppRouteHandlerRoutes = "/api/download" |
"/api/webhooks/lemonsqueezy" | "/auth/callback" | "/blog/feed.xml"
Next.js also exposes global PageProps, LayoutProps and RouteContext helpers built from that union, available with no import. What next build does with all of it — and why this codebase still hand-writes its own params types rather than using the helpers — is the subject of Next.js and TypeScript: what next build actually generates. This post stops at the scaffold.
Where the type check actually runs
There is no tsc in this project's package.json:
"scripts": {
"dev": "next dev",
"build": "next build",
"lint": "eslint .",
"format:check": "prettier --check .",
"test": "vitest run"
}
And none in CI either — the GitHub Actions job runs exactly four commands after npm ci: lint, format:check, test, build. next build is the type check. It runs the compiler over the whole project, the custom Next.js checker included, and fails the build on a type error anywhere — including in a file no route imports.
That is a real scaffold default worth deciding about rather than inheriting. Measured on this repo, npm run build takes 31s to compile and prerender 884 pages; npx tsc --noEmit takes 5.5s and catches the same class of error. Six times faster matters more as a project grows, and it matters most in CI, where a type error currently fails the slowest step in the job. Adding
"typecheck": "tsc --noEmit"
and running it before build turns a slow red build into a fast red check. The counterpart trap is typescript.ignoreBuildErrors in next.config.ts: with no separate tsc step, that flag does not relax type checking, it removes it — see TypeScript: ignore type check for what each escape hatch actually suppresses.
What this project changed from the scaffold, and what it did not
Still untouched after 220 typed files: every compilerOptions value above. What got added around it instead:
next.config.ts, not.js— supported out of the box, typed withimport type { NextConfig } from "next".vitest.config.tsandeslint.config.mjs, both at the root and both inside the**/*.tsinclude glob, so a broken config is a type error."server-only"as a real dependency.src/lib/supabase/admin.tsimports it so that pulling a service-role client into a Client Component fails the build rather than shipping.
The one scaffold default genuinely worth deleting once a project is all-TypeScript is allowJs. Leaving it on means a stray .js file silently compiles with no checking at all.
Mistakes and troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
Cannot find module './logo.png' after a fresh clone | next-env.d.ts is gitignored and not regenerated yet | Run next dev, next build or next typegen |
| A CSS import typo never errors | Side-effect imports are not type-resolved | Rely on the build/bundler for those, not tsc |
--typescript "does nothing" | It is already the default | Drop the flag; it is not broken |
Editor misses use client mistakes | Editor is on its bundled TypeScript | Command palette → TypeScript: Select TypeScript Version → Use Workspace Version |
next-env.d.ts keeps reappearing in git status | It was committed before the scaffold's .gitignore rule took effect | git rm --cached next-env.d.ts and leave it ignored |
| A type error only surfaces at the end of CI | next build is the only type check in the job | Add "typecheck": "tsc --noEmit" and run it before build |
Cannot find name 'PageProps' | Route types have not been generated in this checkout | next typegen (0.5s), or just start the dev server |
Frequently asked questions
Do I still need --typescript with create-next-app?
No. npx create-next-app@latest scaffolds TypeScript by default. The flag is accepted but selects what you would get anyway.
How do I add TypeScript to an existing Next.js project?
Rename a file to .ts or .tsx and run next dev or next build. Next.js installs typescript, @types/react and @types/node and writes the recommended tsconfig.json. If you have a jsconfig.json, move its paths across and delete it.
Should next-env.d.ts be committed?
No — Next.js recommends gitignoring it, and create-next-app sets that up. Keep it in the tsconfig.json include array; the file is regenerated on every dev, build or typegen.
Does next build type-check my whole project?
Yes, including files no route imports. That is why this repo has no separate tsc step in CI — though adding one is faster feedback: 5.5s here against a 31s build.
Templates in this post
Every edition below is a create-next-app-shaped Next.js 16 + TypeScript project with the same tsconfig.json discussed above, so the first thing you do in one is npm install, not configuration. ASoc Catalyst is an AI-automation agency marketing site with a service grid, a four-step delivery process and three-tier pricing. ASoc Chain is a DeFi-protocol landing page built around self-custody messaging and an on-chain 3D hero. ASoc Cognition is an AI-consulting landing page covering services, pricing, projects and case results.
Browse the full sets: Next.js landing page templates, Tailwind landing page templates.
