The config file sits beside app/, exports a default object (or a function of the build phase), and is a Node module — never bundled, never sent to the browser. .js, .mjs, .ts and .mts all work. The TypeScript form buys you the NextConfig type, which is the whole reason to prefer it:
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
typedRoutes: true,
imgaes: { qualities: [75] },
}
export default nextConfig$ npx tsc --noEmit next.config.ts(5,3): error TS2561: Object literal may only specify known properties, but 'imgaes' does not exist in type 'NextConfig'. Did you mean to write 'images'?
A wrong value — reactStrictMode: 'yes' — is caught the same way, and catching it here matters: if it reaches the bundler instead, 16.3.5 aborts with a Rust panic from Turbopack 10,514 's config deserializer rather than a readable message. A JavaScript config gets the same editor checking from // @ts-check and a /** @type {import('next').NextConfig} */ comment.
Module resolution here is CommonJS, so __dirname works but top-level await and dynamic import() do not, unless Node's native TypeScript resolver is active (Node 22.18 and later, detected through process.features.typescript). In a CommonJS project, name the file next.config.mts for ESM semantics. The build reports how long the file took to evaluate — ✓ Running next.config.ts took 48ms — which matters once the config calls a flag service.
Building with the check switched off
next build stops at the first type error, before generating a single page:
✓ Compiled successfully in 305ms
Running TypeScript ...
app/products/[sku]/page.tsx(2,11): error TS2339: Property 'slug' does not exist on
type '{ sku: string; }'.
Failed to type check.Add typescript: { ignoreBuildErrors: true }, run the same tree again, and it ships:
✓ Compiled successfully in 248ms Skipping validation of types ✓ Generating static pages using 9 workers (7/7) in 746ms
The page renders undefined where slug was, and nobody hears about it until a user does. If you use the option, run tsc --noEmit elsewhere in the pipeline. typescript.tsconfigPath is usually the better answer: keep tsconfig.json strict for your editor and point the build at a tsconfig.build.json that relaxes exactly the options you must.