typedRoutes
Type-check href values against your app's real routes at compile time.
typedRoutes?: boolean // default: false- Added in Next.js v13.2.0
Parameters
| Parameter | Type | Description |
|---|---|---|
$typedRoutesoptional | boolean= false | Generates route types in .next/types and type-checks literal href strings passed to next/link and next/navigation methods. Requires TypeScript. |
Errors
Nothing throws at runtime; an href that is not a known route is a TypeScript error in the editor and fails next build type checking.
Changelog
- PHP v13.2.0Statically typed links introduced in beta as experimental.typedRoutes.
- PHP v15.0.0next.config.ts support added, so the option can be written with the NextConfig type.
typedRoutes makes Next.js emit a declaration file describing every route in your app, then uses it to type the href prop of next/link. In the App Router it also types the push, replace and prefetch methods returned by useRouter. Pages Router next/router methods are not typed.
When to reach for it
Turn it on in any TypeScript app where routes get renamed or deleted. A moved folder under app/ becomes a compile error at every link that pointed at it, instead of a 404 someone finds in production.
What trips people up
- Only literal strings are checked. A value built at runtime needs a cast:
href={('/blog/' + slug) as Route}. Template literals with dynamic segments are fine. - The generated types live in
.next/types. If the project was not created bycreate-next-app, add".next/types/**/*.ts"toincludeintsconfig.jsonor everyhrefresolves tonever. - Routes only reachable through
proxy.tsrewrites or redirects do not exist as files, so they are not validRoutevalues and need a cast. - A component that wraps
Linkmust accept a genericRoute<T>, otherwise callers lose the checking.
Example
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
typedRoutes: true,
}
export default nextConfigWhen to use it
- Catch links to a route you just renamed or deleted, at build time instead of in production.
- Give editors autocomplete for internal paths in a nav or breadcrumb data structure.
- Enforce that a shared Link wrapper only accepts real application routes.
Frequently asked questions
Why does typedRoutes say my valid href is not a Route?
The generated types are not being loaded — add ".next/types/**/*.ts" to the include array in tsconfig.json and run next dev, next build, or next typegen so the declaration file exists. Paths that only exist via a proxy rewrite are also not real routes and need `as Route`.
Does typedRoutes work without TypeScript?
No. The feature is entirely type-level, so it requires a TypeScript project; in a JavaScript project the flag does nothing useful.
How do I type an href that is built from a variable?
Cast it with `as Route` from the `next` package, for example router.push(('/blog/' + slug) as Route). Template literals containing a dynamic segment, like `/blog/${slug}`, are validated without a cast.
More examples
Including the generated types in tsconfig.json
Without the .next/types entry TypeScript never sees the generated route union, so valid links are reported as errors and invalid ones are not caught. create-next-app writes this include array for you.
{
"include": [
"next-env.d.ts",
".next/types/**/*.ts",
"**/*.ts",
"**/*.tsx"
],
"exclude": ["node_modules"]
}Literal and non-literal hrefs in a Client Component
The third Link fails type checking because /aboot has no matching folder under app/. The concatenated string is not a literal, so it needs `as Route` — the cast is the escape hatch, and it is also how you point at a path that only exists after a proxy rewrite.
'use client'
import type { Route } from 'next'
import Link from 'next/link'
import { useRouter } from 'next/navigation'
export default function Example() {
const router = useRouter()
const slug = 'nextjs'
return (
<>
<Link href="/about">About</Link>
<Link href={`/blog/${slug}`}>Post</Link>
{/* TypeScript error: '/aboot' is not a route */}
<Link href="/aboot">Typo</Link>
<button onClick={() => router.push('/about')}>Push About</button>
<button onClick={() => router.push(('/blog/' + slug) as Route)}>
Push concatenated
</button>
</>
)
}A wrapper component that keeps the checking
Typing the prop as `string` would silently discard route checking for every call site. The generic Route<T> forwards the constraint, so <Card href="/aboot" /> still errors at the caller.
import type { Route } from 'next'
import Link from 'next/link'
export function Card<T extends string>({
href,
title,
}: {
href: Route<T> | URL
title: string
}) {
return (
<Link href={href}>
<div>{title}</div>
</Link>
)
}
Discussion