typedRoutes

Type-check href values against your app's real routes at compile time.

Signature
typedRoutes?: boolean // default: false
  • Added in Next.js v13.2.0

Parameters

ParameterTypeDescription
$typedRoutesoptionalboolean= falseGenerates 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 by create-next-app, add ".next/types/**/*.ts" to include in tsconfig.json or every href resolves to never.
  • Routes only reachable through proxy.ts rewrites or redirects do not exist as files, so they are not valid Route values and need a cast.
  • A component that wraps Link must accept a generic Route<T>, otherwise callers lose the checking.

Example

Example · typescript
import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
  typedRoutes: true,
}

export default nextConfig

When 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.

Example · json
{
  "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.

Example · tsx
'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.

Example · tsx
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

  • Be the first to comment on this lesson.