Jedyny źródło prawdy dotyczące ścieżek tras w aplikacjach Next.js
Dowiedz się, w jaki sposób moduł typowanego routingu centralnego zastępuje rozproszone ścieżki stringowe w Next.js, dzięki funkcjom pomocniczym do dynamicznych segmentów oraz grupowanego eksportu tras.
Większość projektów Next.js rozpoczyna się od wpisywania bezpośrednio ścieżek do komponentów Link oraz wywołań router.push. Jest to szybkie przy pierwszych kilku stronach, ale w miarę rozwoju aplikacji te same łańcuchy tekstu są powtarzane w menu nawigacyjnym, stopkach strony, przekierowaniach i e-mailach, a każda zmiana nazwy staje się zadaniem polegającym na wyszukiwaniu i zastępowaniu. Problem pogłębia się, gdy asystenci do pisania kodu oparte na AI generują część kodu, ponieważ rzadko konsekwentnie przestrzegają jednej konwencji. Przeniesienie wszystkich ścieżek do jednego modułu naprawia tę sytuację.
Jakie problemy powstają przy ścieżkach wstawionych bezpośrednio
Oto typowy komponent nawigacyjny, w którym każde miejsce docelowe to dosłowny łańcuch tekstu:
// ❌ Problematic approach
import Link from 'next/link'
import { useRouter } from 'next/router'
export default function Navbar() {
const router = useRouter()
const handleNavigation = () => {
router.push('/dashboard/user/profile')
}
return (
<nav>
<Link href="/about">About</Link>
<Link href="/contact">Contact</Link>
<Link href="/blog">Blog</Link>
<Link href="/dashboard/analytics">Analytics</Link>
<button onClick={handleNavigation}>Profile</button>
</nav>
)
}
Obecnie nic tu nie jest nieprawidłowe, ale w miarę rozwoju bazy kodu i zespołu pojawiają się cztery problemy.
Zmiana nazw staje się trudnym zadaniem
Zmiana /blog na /articles oznacza konieczność znalezienia wszystkich wystąpień tego adresu w komponentach, kodzie serwera, testach oraz konfiguracji. Pominięcie któregokolwiek z nich powoduje powstanie nieaktywnego linku.
Błędy pisowni umykają uwadze
Adres taki jak /dashbord to po prostu ciąg znaków, więc ani kompilator, ani narzędzie do sprawdzania kodu ich nie identyfikują. Błąd zwykle objawia się błędem 404, czasami tylko w środowisku produkcyjnym.
Edytor nie może pomóc
Ponieważ trasy to niestrukturyzowane ciągi znaków, nie ma funkcji autodopasowywania, opcji „przejść do definicji” ani ostrzeżeń, gdy dana trasa przestaje istnieć.
Członkowie zespołu działają niezgodnie
Różne osoby oraz różne polecenia dla sztucznej inteligencji skutkują tym, że ten sam adres jest zapisywany w nieco inny sposób: z lub bez końcowej kreski, z ręcznie utworzoną ciągłą wartością itp.
Centralizacja tras w jednym module
Rozwiązaniem jest pojedynczy plik konfiguracyjny, który importuje każda część aplikacji.
Gdzie znajduje się plik
Katalog constants lub config umieszczony obok katalogów app i components ułatwia jego znalezienie:
/src
/app
/components
/constants
└── routes.ts
/lib
Definicja obiektu tras
Poniższy moduł grupuje trasy według obszarów. Strony statyczne to zwykłe łańcuchy znaków. Strony dynamiczne to małe funkcje, które przyjmują segment zmiennych i zwracają pełną ścieżkę, dzięki czemu użytkownicy nigdy nie muszą ręcznie budować adresów URL. Punkty końcowe API znajdują się pod zagnieżdżonym kluczem API, aby można je było używać w wywołaniach fetch. Słowa kluczowe as const pozwalają TypeScriptowi wywnioskować dokładne wartości literowe i oznaczają obiekt jako tylko do odczytu, co umożliwia precyzyjne autodopowiadanie i zapobiega przypadkowej przypisaniu nowych wartości.
// constants/routes.ts
export const ROUTES = {
// Public routes
HOME: '/',
ABOUT: '/about',
CONTACT: '/contact',
PRICING: '/pricing',
// Auth routes
LOGIN: '/auth/login',
REGISTER: '/auth/register',
FORGOT_PASSWORD: '/auth/forgot-password',
// Blog routes
BLOG: '/blog',
BLOG_DETAIL: (slug: string) => `/blog/${slug}`,
BLOG_CATEGORY: (category: string) => `/blog/category/${category}`,
// Dashboard routes
DASHBOARD: '/dashboard',
DASHBOARD_ANALYTICS: '/dashboard/analytics',
DASHBOARD_SETTINGS: '/dashboard/settings',
DASHBOARD_PROFILE: '/dashboard/profile',
// Dynamic user routes
USER_PROFILE: (userId: string) => `/user/${userId}`,
USER_POSTS: (userId: string) => `/user/${userId}/posts`,
// API routes (useful for fetch calls)
API: {
USERS: '/api/users',
POSTS: '/api/posts',
AUTH: '/api/auth',
},
} as const
// Export route groups for better organization
export const AUTH_ROUTES = {
LOGIN: ROUTES.LOGIN,
REGISTER: ROUTES.REGISTER,
FORGOT_PASSWORD: ROUTES.FORGOT_PASSWORD,
} as const
export const DASHBOARD_ROUTES = {
HOME: ROUTES.DASHBOARD,
ANALYTICS: ROUTES.DASHBOARD_ANALYTICS,
SETTINGS: ROUTES.DASHBOARD_SETTINGS,
PROFILE: ROUTES.DASHBOARD_PROFILE,
} as const
Dwa dodatkowe elementy eksportowane, AUTH_ROUTES i DASHBOARD_ROUTES, stanowią widoki głównego obiektu przeznaczone dla kodu, który zajmuje się tylko jedną dziedziną – na przykład middleware chroniące panel sterowania. Ponieważ odnoszą się one do ROUTES zamiast powtarzać ciągi znaków, nadal istnieje tylko jedna definicja na każdą ścieżkę.
Jedna ulepszenie, które warto rozważyć: jeśli identyfikator lub ID może zawierać spacje lub specjalne znaki, należy je zamknąć w funkcji encodeURIComponent wewnątrz helpera, aby każdy użytkownik automatycznie otrzymywał bezpieczną adres URL.
Używanie ścieżek w komponentach
Teraz navbar importuje nazwy zamiast ciągów znaków. Zarówno Link, jak i nawigacja imperatywna pobierają dane z tego samego modułu, więc nie mogą się ze sobą kłócić:
// components/Navbar.tsx
import Link from 'next/link'
import { useRouter } from 'next/router'
import { ROUTES, DASHBOARD_ROUTES } from '@/constants/routes'
export default function Navbar() {
const router = useRouter()
const handleProfileNavigation = () => {
router.push(DASHBOARD_ROUTES.PROFILE)
}
return (
<nav className="flex space-x-4">
<Link href={ROUTES.ABOUT} className="hover:text-blue-500">
About
</Link>
<Link href={ROUTES.CONTACT} className="hover:text-blue-500">
Contact
</Link>
<Link href={ROUTES.BLOG} className="hover:text-blue-500">
Blog
</Link>
<Link href={DASHBOARD_ROUTES.ANALYTICS} className="hover:text-blue-500">
Analytics
</Link>
<button
onClick={handleProfileNavigation}
className="rounded bg-blue-500 px-4 py-2 text-white"
>
Profile
</button>
</nav>
)
}
Należy zauważyć, że ten przykład importuje funkcję useRouter z modułu next/router, który należy do systemu Pages Router. W projektach App Router należy ją importować z modułu next/navigation wewnątrz komponentu klienckiego; moduł routes funkcjonuje w ten sam sposób niezależnie od wyboru źródła.
Budowanie dynamicznych linków
Dynamiczne segmenty to obszar, w którym ten model sprawdza się najlepiej. Poniższy wykaz zawiera linki do każdego wpisu oraz jego kategorii poprzez wywoływanie funkcji pomocniczych z danymi pochodzącymi bezpośrednio z wpisu:
// components/BlogList.tsx
import Link from 'next/link'
import { ROUTES } from '@/constants/routes'
interface Post {
slug: string
title: string
category: string
}
interface BlogListProps {
posts: Post[]
}
export default function BlogList({ posts }: BlogListProps) {
return (
<div className="space-y-4">
{posts.map((post) => (
<article key={post.slug} className="rounded border p-4">
<h2 className="text-xl font-bold">
<Link href={ROUTES.BLOG_DETAIL(post.slug)} className="hover:text-blue-500">
{post.title}
</Link>
</h2>
<Link
href={ROUTES.BLOG_CATEGORY(post.category)}
className="text-sm text-gray-500 hover:text-blue-500"
>
Category: {post.category}
</Link>
</article>
))}
</div>
)
}
Jeśli struktura URL dla wpisów kiedykolwiek ulegnie zmianie, na przykład poprzez dodanie segmentu z rokiem, wystarczy zaktualizować BLOG_DETAIL raz, a wszystkie funkcje korzystające z niego automatycznie się dostosują. TypeScript również poinformuje o błędzie, jeśli zostanie przekazana niewłaściwa liczba argumentów.
Korzyści
- Każda ścieżka jest definiowana tylko raz i wykorzystywana wszędzie.
Ograniczenia i alternatywy
Plik zmiennych nie sprawdza, czy odpowiadająca mu strona rzeczywiście istnieje w katalogu app lub pages. Jeśli folder zostanie przemianowany bez aktualizacji modułu, linki nadal przestaną działać. Next.js oferuje również opcję typowanych tras, która generuje typy na podstawie systemu plików; sprawdź aktualną dokumentację, aby dowiedzieć się o jej stanie i tym, czy pasuje do twojej konfiguracji. Wiele zespołów łączy obie metody: generowane typy dla poprawności, a moduł zmiennych dla czytelnych nazw i narzędzi pomocniczych.
Gdy asystenci AI przyczyniają się do tworzenia kodu, poinformuj ich w instrukcjach projektu, aby importowali elementy z modułu tras.
Podsumowanie
Traktuj URL-y jako część API twojego aplikacji, a nie rozproszone łańcuchy znaków. Typowany, scentralizowany moduł tras przekształca zmiany nazw w pojedyncze edycje, ujawnia błędy w edytorze i zapewnia zarówno ludziom, jak i narzędziom jedno oczywiste miejsce do poszukiwań.
Literatura pokrewna
- Migracja z next/router: Parametry, płytkie adresy URL i błędy 404 w App Router — Dowiedz się, jak parametry, funkcja useParams, aktualizacje płytkich adresów URL, nawigacja programowa oraz prawdziwe strony błędów 404 funkcjonują w Next.js App Router po usunięciu next/router.
- Działania serwera czy przetwarzacze trasy? Przewodnik decyzyjny dla Next.js 16 — Poznaj, kiedy mutacja w Next.js 16 powinna znajdować się w działaniu serwera, a kiedy wymaga przetwarzacza trasy, z poprawionymi przykładami dotyczącymi formularzy, błędów, mechanizmu optymizmu oraz webhooków.