Єдине джерело істини для маршрутів у застосунках Next.js
Дізнайтеся, як модуль централізованих маршрутів з введенням тексту замінює розкидані рядкові шляхи в Next.js, за допомогою функцій-допоміжників для динамічних сегментів та групового експорту маршрутів.
Більшість проєктів Next.js починаються з введення шляхів безпосередньо у компоненти Link та функції router.push. Це швидко для перших кількох сторінок, але коли додаток росте, однакові рядки починають дублюватися у навігаційних панелях, футерах, перенаправленнях та електронних листах, і кожна заміна назви стає складним завданням пошуку та заміни. Проблема погіршується, коли асистенти з кодування на основі ШІ генерують частину коду, оскільки вони рідко послідовно дотримуються єдиних правил. Перенесення всіх шляхів у один модуль вирішує цю проблему.
Що не так із вбудованими шляхами
Ось типовий компонент навігації, де кожна точка призначення є буквальним рядком:
// ❌ 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>
)
}
Зараз тут нічого не не так, але з ростом кодової бази та команди з’являються чотири проблеми.
Заміна назв стає складним завданням
Заміна /blog на /articles означає необхідність пошуку кожного випадку використання цього шляху у компонентах, коді сервера, тестах та конфігурації. Якщо щось пропустити, залишиться недійсне посилання.
Друкарські помилки залишаються непоміченими
Шлях на кшталт /dashbord є просто рядком тексту, тому ні компілятор, ні інструменти перевірки не можуть його розпізнати. Помилка зазвичай проявляється як код 404, іноді лише у продакшен-середовищі.
Редактор не може допомогти
Оскільки маршрути є неструктурованими рядками, немає функції автодоповнення, можливості „перейти до визначення“ та попереджень про те, що маршрут більше не існує.
Члени команди розходяться у підходах
Різні люди та різні запити до ШІ призводять до того, що одну й ту саму мету записують трохи по-різному: з крапкою чи без неї, із вручну створеним параметром запиту тощо.
Централізація маршрутів у одному модулі
Рішення полягає у єдиному файлі конфігурації, який імпортується кожною частиною додатку.
Де знаходиться файл
Папка constants або config, розташована поруч із app та components, полегшує його пошук:
/src
/app
/components
/constants
└── routes.ts
/lib
Визначення об’єкта маршрутів
Наведений нижче модуль групує маршрути за категоріями. Статичні сторінки — це звичайні рядки. Динамічні сторінки — це невеликі функції, які приймають змінну частину та повертають повний шлях, тож користувачі ніколи не створюють URL вручну. Кінцеві точки API розташовані під вкладеною ключем API для використання у викликах fetch. Додавання as const дозволяє TypeScript визначити точні літеральні значення та позначає об’єкт як лише для читання, що забезпечує точне автодоповнення та запобігає випадковому переатрибуванню.
// 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
Два додаткові експорти, AUTH_ROUTES та DASHBOARD_ROUTES, є представленнями основного об’єкта для коду, який цікавиться лише однією областю, наприклад мідлвейру, що захищає панель керування. Оскільки вони посилаються на ROUTES, а не повторюють рядки, залишається лише одне визначення на кожен шлях.
Одне вдосконалення, яке варто розглянути: якщо слаг або ID можуть містити пробіли чи спеціальні символи, потрібно обгорнути його у encodeURIComponent усередині допоміжної функції, щоб кожен користувач автоматично отримував безпечну URL.
Використання маршрутів у компонентах
Тепер navbar імпортує назви замість рядків. І Link, і імперативне навігування читають дані з того самого модуля, тому вони ніколи не можуть суперечити один одному:
// 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>
)
}
Зауважте, що в цьому прикладі імпортується useRouter з next/router, який належить до Pages Router. У проектах App Router його слід імпортувати з next/navigation усередині клієнтського компонента; модуль маршрутів працює однаково в обох випадках.
Створення динамічних посилань
Динамічні сегменти — це те, де ця модель дає найбільшу користь. У списку нижче є посилання на кожну публікацію та її категорію шляхом виклику допоміжних функцій із даними з публікації:
// 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>
)
}
Якщо структура URL для публікацій коли-небудь зміниться, наприклад, шляхом додавання сегмента з роком, вам потрібно буде оновити BLOG_DETAIL один раз, і всі виклики будуть слідувати за цим. TypeScript також позначить будь-який виклик, який передає неправильну кількість аргументів.
Що ви отримуєте
- Кожен шлях визначається один раз та використовується скрізь.
Обмеження та альтернативи
Файл констант не перевіряє, чи справді існує відповідна сторінка у каталозі app або pages. Якщо папку перейменувати без оновлення модуля, посилання все одно перестануть працювати. Next.js також пропонує опцію типованих маршрутів, яка генерує типи з файлової системи; перевірте поточну документацію щодо її статусу та того, чи підходить вона вашій конфігурації. Багато команд поєднують обидва підходи: генеровані типи для забезпечення коректності та модуль констант для зрозумілих імен та допоміжних функцій.
Коли штучні інтелектуальні асистенти допомагають писати код, уточніть у інструкціях до проекту, що потрібно імпортувати з модуля маршрутів.
Підсумок
Розглядайте URL як частину API вашого додатку, а не просто розкидані рядки. Типований, централізований модуль маршрутів дозволяє здійснювати зміни через одне оновлення, виявляти помилки у редакторі та забезпечує як людям, так і інструментам одне очевидне місце для пошуку інформації.