Next.js and TypeScript: What next build Actually Generates
This repo generates real route types on every build. What .next/types actually contains, and why this project still hand-writes its own params types instead of using the new helpers.
Every "Next.js with TypeScript" guide covers the same setup: rename a file to .tsx, run next dev, get a tsconfig.json for free. What almost none of them mention is that a plugin line in that generated config — "plugins": [{ "name": "next" }] — is doing two separate jobs at once: an IDE-only language-service plugin, and a build-time type checker that validates every page and layout against routes Next.js only knows about because it just built your app.
The short answer
Adding TypeScript to a Next.js project is automatic: create a .ts/.tsx file, and the next next dev or next build generates a tsconfig.json with the right compiler options and installs the missing @types packages for you. The part worth understanding past that first step is what Next.js's own TypeScript plugin does beyond generic type-checking — it generates route types from your actual file tree and validates every page's exports against them, which is a Next.js-specific layer no other framework's TypeScript setup replicates. This repo's own tsconfig.json and a real next build show exactly what that layer contains.
What Next.js actually generates, versus what a generic guide shows
This project's tsconfig.json compiler options are ordinary TypeScript — strict: true, moduleResolution: "bundler", jsx: "react-jsx" — except for one entry most tutorials gloss over:
// tsconfig.json
{
"compilerOptions": {
"plugins": [{ "name": "next" }]
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
".next/dev/types/**/*.ts",
"**/*.mts"
]
}
plugins wires in Next's custom TypeScript language-service plugin — the thing that makes your editor warn if you use useState in a file missing "use client", or flag an invalid export const revalidate value. That part runs only in your editor. The include array is the other half, and it's the one that actually changes what next build's type-check step sees: two globs pointing at .next/types, a directory this repo doesn't write by hand — Next.js generates it on every next dev and next build.
Running a real build against this repo produces exactly three files there:
.next/types/routes.d.ts # every real route, as a string-literal union
.next/types/validator.ts # checks each page/layout exports the right shape
.next/types/cache-life.d.ts # typed profiles for the "use cache" directive
routes.d.ts is the interesting one. It's not a template — it's generated fresh from whatever routes your app/ directory actually contains:
// .next/types/routes.d.ts (generated — do not edit)
type AppRoutes =
| "/"
| "/blog"
| "/blog/[slug]"
| "/changelog"
| "/contact"
| "/dashboard"
| "/dashboard/settings"
| "/docs"
// ...27 entries total, one per real route this build found
| "/terms"
Twenty-seven literal routes, because that's how many this storefront actually has. Add a route and rebuild; the union grows by one. Delete one, and every string that referenced it stops compiling. validator.ts then cross-checks the file: it types-checks that app/blog/[slug]/page.tsx's default export, generateStaticParams, and generateMetadata all agree on the shape of params for exactly that route — not a generic { params: any }, the real { slug: string } this route's folder name implies.
None of this reaches your code by explicit import, either. next-env.d.ts — the other file create-next-app generates and every guide tells you never to edit — is what wires it in:
// next-env.d.ts
/// <reference types="next" />
/// <reference types="next/image-types/global" />
import "./.next/types/routes.d.ts";
A side-effect import of a .d.ts file pulls in its ambient type declarations globally, with nothing to import by name at the call site. That's why routes.d.ts's AppRoutes union and the route-aware helper types are available everywhere in this project without an import type line anywhere — the connection lives in a three-line file most contributors never open.
None of that exists in a plain Vite+React+TypeScript setup, or in any of the "add TypeScript to Next.js" walkthroughs that top the search results for this exact query — they cover the compiler options create-next-app writes, not the route-aware layer that only shows up once you actually run a build.
What this repo does — and doesn't — take from that layer
Next.js 16 also ships global PageProps/LayoutProps helper types, generated from that same route union, so a page can write PageProps<'/blog/[slug]'> instead of declaring its own params type. This codebase doesn't use them — every dynamic page hand-writes its own narrow type instead:
// src/app/blog/[slug]/page.tsx
type Params = { slug: string };
export async function generateMetadata({
params,
}: {
params: Promise<Params>;
}): Promise<Metadata> {
const { slug } = await params;
// ...
}
That's a deliberate-looking choice, not an oversight: Params is a two-line type, explicit at the top of the file, readable without knowing Next's global-helper convention exists. The tradeoff is that nothing forces Params to stay in sync with the folder name [slug] the way the generated ParamMap["/blog/[slug]"] would — rename the folder to [postSlug] and this file keeps compiling with the old field name until something actually calls it wrong. The generated route union in routes.d.ts still catches the broader class of error (a typo'd route string in a <Link href> or a redirect), just not this narrower one inside a single page's own params.
One more thing worth naming precisely, because guides frequently conflate two related-but-different features: typedRoutes — the config flag that makes <Link href="/typo"> itself a type error — is a separate, still-opt-in setting (typedRoutes: true in next.config.ts). This repo's next.config.ts doesn't set it. The routes.d.ts/validator.ts/cache-life.d.ts generation above happens regardless, because it backs the page/layout validator and the global helper types, not the <Link>-checking feature. Turning typedRoutes on here would be the next increment, not something this build already does.
Why isolatedModules and strict aren't optional here
Two more entries in this project's tsconfig.json exist specifically because of how Next.js compiles TypeScript, not because of anything React-specific:
{
"isolatedModules": true,
"strict": true
}
next build's default compiler is SWC, not tsc — a per-file, whole-program-unaware Rust transpiler, covered in more depth in TypeScript vs. TypeScript SWC. isolatedModules tells the TypeScript checker to reject any code a single-file transpiler couldn't correctly compile on its own — const enum, and type-only re-exports written without the type keyword, are the two that actually bite in practice. strict is what makes the separate tsc type-check pass (also covered there) worth running at all; without it, next build's "Finished TypeScript" line still prints, but it's checking far less.
Mistakes and troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
Property 'X' does not exist on type 'never' on a route helper | The route in routes.d.ts is stale — you added a page after the last build | Delete .next and rebuild, or run next dev again so the route types regenerate |
A typo'd <Link href="/blogg"> compiles fine | typedRoutes isn't enabled — the default route generation backs page/layout validation, not <Link> checking | Add typedRoutes: true to next.config.ts if you want that specific check |
.next/types files show up in a diff or git status | They're build output, not source — should be gitignored | Confirm .next/ is in .gitignore; never hand-edit anything under .next/types |
A page's generateMetadata params type "doesn't match" after a folder rename | A hand-written params type (like this repo's Params) doesn't auto-update with the folder name the way the generated helpers would | Rename the type's field to match, or switch that page to the generated PageProps<'/route'> helper so the mismatch becomes a compile error instead of a silent stale type |
next build's TypeScript step passes locally but CI reports different errors | A stale .next/types directory committed or cached between environments | Never commit .next/; let each environment regenerate its own route types from a clean build |
Frequently asked questions
Do I need to configure anything to get TypeScript working in Next.js?
No — creating a .ts/.tsx file and running next dev or next build generates tsconfig.json and installs the missing type packages automatically. This repo's own file only diverges from that default by adding strict, isolatedModules, and the @/* path alias — all optional hardening, not requirements.
Is the .next/types directory something I should look at directly?
Only to understand what's happening — never to edit it. It's regenerated on every next dev and next build from your actual app/ tree, and this repo doesn't commit it (it's build output, like the rest of .next/).
What's the difference between the Next.js TypeScript plugin and typedRoutes?
The plugin (wired via tsconfig.json's plugins array) and the automatic .next/types generation validate your page and layout exports against your real routes — that runs by default. typedRoutes: true is a separate, still-opt-in next.config.ts flag that extends type-checking to <Link href> strings themselves. This repo uses the first and not the second.
Does using TypeScript with Next.js slow down the build?
The type-check is a separate pass from SWC's compile step (✓ Compiled successfully happens first, Running TypeScript after), so a type error can't stop your dev server but will fail next build. On this repo — 27 routes, ~220 source files — that separate check adds single-digit seconds to a production build.
Templates in this post
ASoc Mind is the home variant of an AI marketing-solutions site, built around a marketing-intelligence hero and an eight-service image-AI capability grid. ASoc Momentum markets an AI consulting agency with a services stack spanning natural-language processing, integration, and data analysis. ASoc Neuron markets a neural-network platform with a six-capability grid covering deep learning, computer vision, and model deployment.
Browse the full sets: Next.js landing page templates, Tailwind landing page templates.
