Używamy cookies, żeby zwiększyć Twoje doświadczenia na stronie
CodeWorlds

Typed Routes - Bezpieczne linki z TypeScript

W precyzyjnych systemach nawigacyjnych Metropolii Quantum 2150, gdzie każdy błędny link może prowadzić do katastrofy, poznasz Typed Routes - eksperymentalną funkcję Next.js, która zapewnia type-safety dla wszystkich ścieżek w aplikacji.

Problem: Błędy w ścieżkach

Bez Typed Routes, ścieżki są zwykłymi stringami:

1// ❌ Łatwo o literówkę - brak walidacji w TypeScript
2<Link href="/prodcuts">Produkty</Link>  // Literówka!
3<Link href="/users/123/setings">Ustawienia</Link>  // Znowu literówka!
4
5// ❌ Brak sprawdzania parametrów
6redirect('/products/' + productId);  // Co jeśli productId jest undefined?

Rozwiązanie: Typed Routes

Typed Routes automatycznie generuje typy dla wszystkich ścieżek w aplikacji.

Włączenie Typed Routes

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

Po włączeniu i uruchomieniu

next dev
lub
next build
, Next.js wygeneruje plik
.next/types/link.d.ts
z typami.

Podstawowe użycie

Link z walidacją

1import Link from 'next/link';
2
3function Navigation() {
4  return (
5    <nav>
6      {/* ✅ Poprawne ścieżki - TypeScript OK */}
7      <Link href="/">Home</Link>
8      <Link href="/products">Produkty</Link>
9      <Link href="/about">O nas</Link>
10
11      {/* ❌ Błędna ścieżka - TypeScript Error! */}
12      <Link href="/prodcuts">Produkty</Link>
13      {/* Type '"prodcuts"' is not assignable to type 'Route' */}
14    </nav>
15  );
16}

Dynamiczne segmenty

1// Dla struktury: app/products/[id]/page.tsx
2<Link href="/products/123">Produkt 123</Link>  // ✅ OK
3
4// Dla struktury: app/users/[userId]/posts/[postId]/page.tsx
5<Link href="/users/456/posts/789">Post</Link>  // ✅ OK

useRouter z typami

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 sprawdza poprawność ścieżki
10    router.push(`/products/${productId}/edit`);
11  };
12
13  const handleDelete = () => {
14    // ✅ Poprawne
15    router.push('/products');
16  };
17
18  const handleBroken = () => {
19    // ❌ TypeScript Error jeśli /prodcts nie istnieje
20    router.push('/prodcts');
21  };
22
23  return (
24    <div>
25      <button onClick={handleEdit}>Edytuj</button>
26      <button onClick={handleDelete}>Usuń</button>
27    </div>
28  );
29}

redirect() z typami

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

Pomocnik Route

Next.js generuje typ

Route
który możesz używać:

1import type { Route } from 'next';
2
3// Funkcja akceptująca tylko poprawne ścieżki
4function navigateTo(path: Route) {
5  window.location.href = path;
6}
7
8navigateTo('/products');  // ✅ OK
9navigateTo('/prodcuts');  // ❌ Error

Typ dla dynamicznych ścieżek

1import type { Route } from 'next';
2
3// Funkcja generująca link do produktu
4function getProductUrl(id: string): Route {
5  return `/products/${id}` as Route;
6}
7
8// Użycie
9<Link href={getProductUrl('123')}>Produkt</Link>

Praca z parametrami wyszukiwania

1import Link from 'next/link';
2
3function ProductFilters() {
4  return (
5    <div>
6      {/* Ścieżka z query params */}
7      <Link
8        href={{
9          pathname: '/products',
10          query: { category: 'electronics', sort: 'price' },
11        }}
12      >
13        Elektronika (sortuj po cenie)
14      </Link>
15
16      {/* Lub jako string */}
17      <Link href="/products?category=electronics&sort=price">
18        Elektronika
19      </Link>
20    </div>
21  );
22}

Praktyczny przykład - Nawigacja E-commerce

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: 'Produkty', href: '/products' },
13  { label: 'Kategorie', href: '/categories' },
14  { label: 'O nas', href: '/about' },
15  { label: 'Kontakt', href: '/contact' },
16];
17
18export const userNavigation: NavigationItem[] = [
19  { label: 'Profil', href: '/account/profile' },
20  { label: 'Zamówienia', href: '/account/orders' },
21  { label: 'Ustawienia', 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 wie, że to poprawna ścieżka
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// Użycie
34<Breadcrumbs
35  items={[
36    { label: 'Home', href: '/' },
37    { label: 'Produkty', href: '/products' },
38    { label: 'Elektronika', href: '/products?category=electronics' },
39    { label: 'Smartphone X' },  // Ostatni element bez linku
40  ]}
41/>

Ograniczenia Typed Routes

1. Dynamiczne segmenty

1// ❌ TypeScript nie może sprawdzić wartości dynamicznego segmentu
2const id = getUserInput();
3<Link href={`/products/${id}`}>Produkt</Link>  // Przyjmie każdy string
4
5// ✅ Możesz użyć asercji typu jeśli jesteś pewien
6<Link href={`/products/${id}` as Route}>Produkt</Link>

2. Zewnętrzne linki

1// ❌ Zewnętrzne URL nie są Route
2<Link href="https://google.com">Google</Link>  // Error
3
4// ✅ Użyj zwykłego <a> dla zewnętrznych linków
5<a href="https://google.com">Google</a>

3. Catch-all routes

1// Dla app/docs/[...slug]/page.tsx
2// TypeScript sprawdzi prefix, ale nie całą ścieżkę
3<Link href="/docs/getting-started/installation">Docs</Link>  // ✅

Migracja istniejącego projektu

Krok 1: Włącz typedRoutes

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

Krok 2: Uruchom build lub dev

1npm run dev
2# lub
3npm run build

Krok 3: Napraw błędy TypeScript

1# Sprawdź błędy
2npm run type-check
3
4# Typowe problemy:
5# - Literówki w ścieżkach
6# - Nieistniejące strony
7# - Zewnętrzne linki w <Link>

Debugowanie

Jeśli typy nie działają poprawnie:

1# Usuń wygenerowane typy i wygeneruj ponownie
2rm -rf .next/types
3npm run dev

Sprawdź wygenerowany plik:

1// .next/types/link.d.ts
2// Ten plik zawiera wszystkie wygenerowane typy Route

Podsumowanie

Typed Routes w Next.js zapewniają:

  • Bezpieczeństwo typów - błędy w ścieżkach wykryte w compile-time
  • Autocomplete - IDE podpowiada dostępne ścieżki
  • Refactoring - zmiana struktury routingu automatycznie pokazuje błędy
  • Dokumentacja - typy służą jako dokumentacja dostępnych ścieżek

W precyzyjnych systemach Metropolii Quantum 2150, gdzie każdy błąd nawigacyjny może kosztować miliony, Typed Routes to niezbędne zabezpieczenie dla każdej profesjonalnej aplikacji Next.js!

Przejdź do CodeWorlds