We use cookies to enhance your experience on the site
CodeWorlds

Typed Routes - Safe links with TypeScript

In the precise navigation systems of Metropolis Quantum 2150, where every broken link can lead to catastrophe, you will learn about Typed Routes - an experimental Next.js feature that brings type safety to every path in your application.

Problem: Errors in paths

Without Typed Routes, paths are just plain strings:

1// Easy to typo - no validation in TypeScript
2<Link href="/prodcuts">Products</Link>  // Typo!
3<Link href="/users/123/setings">Settings</Link>  // Another typo!
4
5// No parameter checking
6redirect('/products/' + productId);  // What if productId is undefined?

Solution: Typed Routes

Typed Routes automatically generates types for every path in your application.

Enabling Typed Routes

1// next.config.js
2module.exports = {
3  experimental: {
4    typedRoutes: true,
5  },
6};

After enabling and running

next dev
or
next build
, Next.js will generate a
.next/types/link.d.ts
file containing the types.

Basic usage

Link with validation

1import Link from 'next/link';
2
3function Navigation() {
4  return (
5    <nav>
6      {/* Correct paths - TypeScript OK */}
7      <Link href="/">Home</Link>
8      <Link href="/products">Products</Link>
9      <Link href="/about">About</Link>
10
11      {/* Invalid path - TypeScript Error! */}
12      <Link href="/prodcuts">Products</Link>
13      {/* Type '"prodcuts"' is not assignable to type 'Route' */}
14    </nav>
15  );
16}

Dynamic segments

1// For the structure: app/products/[id]/page.tsx
2<Link href="/products/123">Product 123</Link>  // OK
3
4// For the structure: app/users/[userId]/posts/[postId]/page.tsx
5<Link href="/users/456/posts/789">Post</Link>  // OK

useRouter with types

1'use client';
2
3import { useRouter } from 'next/navigation';
4
5function ProductActions({ productId }: { productId: string }) {
6  const router = useRouter();
7
8  const handleEdit = () => {
9    // TypeScript validates the path
10    router.push(`/products/${productId}/edit`);
11  };
12
13  const handleDelete = () => {
14    // Correct
15    router.push('/products');
16  };
17
18  const handleBroken = () => {
19    // TypeScript Error if /prodcts does not exist
20    router.push('/prodcts');
21  };
22
23  return (
24    <div>
25      <button onClick={handleEdit}>Edit</button>
26      <button onClick={handleDelete}>Delete</button>
27    </div>
28  );
29}

redirect() with types

1import { redirect } from 'next/navigation';
2
3async function ProtectedPage() {
4  const session = await getSession();
5
6  if (!session) {
7    // TypeScript validates the path
8    redirect('/login');
9  }
10
11  if (!session.isVerified) {
12    // Correct
13    redirect('/verify-email');
14  }
15
16  return <Dashboard />;
17}

Route helper

Next.js generates a

Route
type you can use:

1import type { Route } from 'next';
2
3// Function accepting only valid paths
4function navigateTo(path: Route) {
5  window.location.href = path;
6}
7
8navigateTo('/products');  // OK
9navigateTo('/prodcuts');  // Error

Type for dynamic paths

1import type { Route } from 'next';
2
3// Function generating a product link
4function getProductUrl(id: string): Route {
5  return `/products/${id}` as Route;
6}
7
8// Usage
9<Link href={getProductUrl('123')}>Product</Link>

Working with search parameters

1import Link from 'next/link';
2
3function ProductFilters() {
4  return (
5    <div>
6      {/* Path with query params */}
7      <Link
8        href={{
9          pathname: '/products',
10          query: { category: 'electronics', sort: 'price' },
11        }}
12      >
13        Electronics (sort by price)
14      </Link>
15
16      {/* Or as a string */}
17      <Link href="/products?category=electronics&sort=price">
18        Electronics
19      </Link>
20    </div>
21  );
22}

Practical example - E-commerce navigation

1// types/navigation.ts
2import type { Route } from 'next';
3
4export interface NavigationItem {
5  label: string;
6  href: Route;
7  icon?: React.ReactNode;
8}
9
10export const mainNavigation: NavigationItem[] = [
11  { label: 'Home', href: '/' },
12  { label: 'Products', href: '/products' },
13  { label: 'Categories', href: '/categories' },
14  { label: 'About', href: '/about' },
15  { label: 'Contact', href: '/contact' },
16];
17
18export const userNavigation: NavigationItem[] = [
19  { label: 'Profile', href: '/account/profile' },
20  { label: 'Orders', href: '/account/orders' },
21  { label: 'Settings', href: '/account/settings' },
22];
1// components/MainNav.tsx
2import Link from 'next/link';
3import { mainNavigation } from '@/types/navigation';
4
5export function MainNav() {
6  return (
7    <nav className="flex gap-6">
8      {mainNavigation.map((item) => (
9        <Link
10          key={item.href}
11          href={item.href}  // TypeScript knows this is a valid path
12          className="hover:text-blue-500"
13        >
14          {item.label}
15        </Link>
16      ))}
17    </nav>
18  );
19}

Breadcrumbs z Typed Routes

1// components/Breadcrumbs.tsx
2import Link from 'next/link';
3import type { Route } from 'next';
4
5interface BreadcrumbItem {
6  label: string;
7  href?: Route;
8}
9
10interface BreadcrumbsProps {
11  items: BreadcrumbItem[];
12}
13
14export function Breadcrumbs({ items }: BreadcrumbsProps) {
15  return (
16    <nav aria-label="Breadcrumb" className="flex items-center gap-2 text-sm">
17      {items.map((item, index) => (
18        <span key={index} className="flex items-center gap-2">
19          {index > 0 && <span className="text-gray-400">/</span>}
20          {item.href ? (
21            <Link href={item.href} className="text-blue-600 hover:underline">
22              {item.label}
23            </Link>
24          ) : (
25            <span className="text-gray-600">{item.label}</span>
26          )}
27        </span>
28      ))}
29    </nav>
30  );
31}
32
33// Usage
34<Breadcrumbs
35  items={[
36    { label: 'Home', href: '/' },
37    { label: 'Products', href: '/products' },
38    { label: 'Electronics', href: '/products?category=electronics' },
39    { label: 'Smartphone X' },  // Last item without a link
40  ]}
41/>

Typed Routes limitations

1. Dynamic segments

1// TypeScript cannot validate the value of a dynamic segment
2const id = getUserInput();
3<Link href={`/products/${id}`}>Product</Link>  // Accepts any string
4
5// You can use a type assertion if you're sure
6<Link href={`/products/${id}` as Route}>Product</Link>

2. External links

1// External URLs are not Route
2<Link href="https://google.com">Google</Link>  // Error
3
4// Use a plain <a> for external links
5<a href="https://google.com">Google</a>

3. Catch-all routes

1// For app/docs/[...slug]/page.tsx
2// TypeScript checks the prefix but not the full path
3<Link href="/docs/getting-started/installation">Docs</Link>  // OK

Migrating an existing project

Step 1: Enable typedRoutes

1// next.config.js
2module.exports = {
3  experimental: {
4    typedRoutes: true,
5  },
6};

Step 2: Run build or dev

1npm run dev
2# or
3npm run build

Step 3: Fix TypeScript errors

1# Check for errors
2npm run type-check
3
4# Common issues:
5# - Typos in paths
6# - Non-existent pages
7# - External links inside <Link>

Debugging

If the types are not working correctly:

1# Remove generated types and regenerate
2rm -rf .next/types
3npm run dev

Check the generated file:

1// .next/types/link.d.ts
2// This file contains all the generated Route types

Summary

Typed Routes in Next.js provide:

  • Type safety - path errors caught at compile time
  • Autocomplete - the IDE suggests available paths
  • Refactoring - changes to routing structure surface errors automatically
  • Documentation - the types serve as documentation of available paths

In the precise systems of Metropolis Quantum 2150, where every navigation error can cost millions, Typed Routes are an essential safeguard for every professional Next.js application!

Go to CodeWorlds