Skip to main content
ASoc
Tutorial

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.

The ASoc Team8 min read

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

SymptomCauseFix
Property 'X' does not exist on type 'never' on a route helperThe route in routes.d.ts is stale — you added a page after the last buildDelete .next and rebuild, or run next dev again so the route types regenerate
A typo'd <Link href="/blogg"> compiles finetypedRoutes isn't enabled — the default route generation backs page/layout validation, not <Link> checkingAdd typedRoutes: true to next.config.ts if you want that specific check
.next/types files show up in a diff or git statusThey're build output, not source — should be gitignoredConfirm .next/ is in .gitignore; never hand-edit anything under .next/types
A page's generateMetadata params type "doesn't match" after a folder renameA hand-written params type (like this repo's Params) doesn't auto-update with the folder name the way the generated helpers wouldRename 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 errorsA stale .next/types directory committed or cached between environmentsNever 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.

Keep reading

Tutorial8 min read

Next.js UI Library Roundups List Five. This Site Uses Zero.

Zero of shadcn, Radix, MUI, Chakra or daisyUI in this package.json. 92 components run on Tailwind v4 tokens and one enforced import rule instead.

Read more
Tutorial8 min read

The Latest Next.js Version Is 16.3.4. This Repo Runs 16.2.9.

npm carries sixteen dist-tags for `next` and `latest` is only one of them. Which version to be on, measured from a repo that pins the framework and ships it to other people.

Read more
Tutorial12 min read

Automated Product Screenshots with Playwright, 111 Templates Deep

The capture is four lines; deciding what is in frame is the work. Nav-driven page discovery, the is-this-the-product check, and the proxy bug that blanks every Chromium request.

Read more