Skip to main content
ASoc
Tutorial

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.

The ASoc Team8 min read

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"]
}
OptionWhat it is doingWorth changing?
target: "ES2017"Syntax level the compiler assumesRarely — the bundler, not tsc, decides what ships
allowJs: trueLets .js files sit beside .tsYes, once the migration is done. This repo has 0 .js files under src/ and the flag is still on
skipLibCheck: trueSkips type-checking node_modules .d.ts filesKeep it. Turning it off here surfaced 7 errors, all of them inside dependencies
strict: trueThe whole point of the scaffoldNever turn this off
noEmit: truetsc checks types and writes nothingKeep — the bundler emits
moduleResolution: "bundler"Resolves imports the way a bundler doesKeep
isolatedModules: trueEach file must compile alone (forces export type)Keep — it is what makes per-file transpilation safe
jsx: "react-jsx"No import React needed per fileKeep
incremental: trueWrites .tsbuildinfo to speed repeat checksKeep, and confirm it is gitignored
plugins: [{ "name": "next" }]The editor plugin that flags bad use client and segment configKeep; in VS Code also pick "Use Workspace Version"
paths: { "@/*": ["./src/*"] }The alias prompt's answerKeep 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:

FileSizeWhat is in it
routes.d.ts103 linesA union of every route in the app, plus a ParamMap of each route's params
cache-life.d.ts145 linesThe 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 with import type { NextConfig } from "next".
  • vitest.config.ts and eslint.config.mjs, both at the root and both inside the **/*.ts include glob, so a broken config is a type error.
  • "server-only" as a real dependency. src/lib/supabase/admin.ts imports 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

SymptomCauseFix
Cannot find module './logo.png' after a fresh clonenext-env.d.ts is gitignored and not regenerated yetRun next dev, next build or next typegen
A CSS import typo never errorsSide-effect imports are not type-resolvedRely on the build/bundler for those, not tsc
--typescript "does nothing"It is already the defaultDrop the flag; it is not broken
Editor misses use client mistakesEditor is on its bundled TypeScriptCommand palette → TypeScript: Select TypeScript Version → Use Workspace Version
next-env.d.ts keeps reappearing in git statusIt was committed before the scaffold's .gitignore rule took effectgit rm --cached next-env.d.ts and leave it ignored
A type error only surfaces at the end of CInext build is the only type check in the jobAdd "typecheck": "tsc --noEmit" and run it before build
Cannot find name 'PageProps'Route types have not been generated in this checkoutnext 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.

Keep reading

Tutorial8 min read

What's Actually in a Data Dashboard? 59 Views, Counted

9 admin templates, 59 dashboard views, audited from the catalog — what a coded data dashboard actually contains versus a BI tool.

Read more