← Назад к вопросам
Как отследить изменение маршрута на странице?
2.2 Middle🔥 151 комментариев
#Soft Skills и рабочие процессы
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Отслеживание изменений маршрута на странице
Отслеживание маршрутов - это фундаментальная часть современных веб-приложений. Существует несколько подходов, от встроенных в Next.js инструментов до ручного отслеживания History API.
1. Next.js App Router (встроенное решение)
В Next.js 13+ рекомендуемый подход - использование встроенных хуков:
// app/layout.tsx
'use client';
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
import { useEffect } from 'react';
export default function RootLayout({ children }) {
const pathname = usePathname();
const router = useRouter();
const searchParams = useSearchParams();
useEffect(() => {
console.log('URL изменился:', pathname);
console.log('Query параметры:', Object.fromEntries(searchParams));
// Аналитика
gtag.pageview({
page_path: pathname,
page_title: document.title,
});
}, [pathname, searchParams]);
return (
<html>
<body>{children}</body>
</html>
);
}
Различие между usePathname и useRouter
import { usePathname, useRouter } from 'next/navigation';
export function NavigationExample() {
const pathname = usePathname(); // Текущий путь - только для чтения
const router = useRouter(); // Объект для навигации
return (
<div>
<p>Текущий путь: {pathname}</p>
{/* Переход на новый маршрут */}
<button onClick={() => router.push('/about')}>
О нас
</button>
{/* Замена текущего маршрута (без истории) */}
<button onClick={() => router.replace('/login')}>
Вход
</button>
{/* Обновить текущую страницу */}
<button onClick={() => router.refresh()}>
Обновить
</button>
</div>
);
}
2. React Router (для Non-Next.js приложений)
// main.tsx
import { useLocation, useNavigate } from 'react-router-dom';
import { useEffect } from 'react';
function AppLayout() {
const location = useLocation();
const navigate = useNavigate();
useEffect(() => {
console.log('Маршрут изменился на:', location.pathname);
console.log('Query параметры:', location.search);
console.log('State:', location.state);
// Логирование в аналитику
trackPageView(location.pathname);
}, [location]);
return (
<div>
<nav>
<button onClick={() => navigate('/home')}>Главная</button>
<button onClick={() => navigate('/about', { state: { from: 'nav' } })}>
О нас
</button>
</nav>
<Outlet /> {/* Содержимое маршрута */}
</div>
);
}
3. History API (низкоуровневый подход)
Если нужно отследить изменения без фреймворка:
// useRouteChange.ts
import { useEffect, useCallback } from 'react';
function useRouteChange(callback) {
useEffect(() => {
// Отслеживание pushState и replaceState
const originalPushState = window.history.pushState;
const originalReplaceState = window.history.replaceState;
window.history.pushState = function (...args) {
originalPushState.apply(window.history, args);
callback(window.location.pathname);
};
window.history.replaceState = function (...args) {
originalReplaceState.apply(window.history, args);
callback(window.location.pathname);
};
// Отслеживание кнопок браузера (back/forward)
const handlePopState = () => {
callback(window.location.pathname);
};
window.addEventListener('popstate', handlePopState);
return () => {
window.history.pushState = originalPushState;
window.history.replaceState = originalReplaceState;
window.removeEventListener('popstate', handlePopState);
};
}, [callback]);
}
// Использование
function App() {
useRouteChange((pathname) => {
console.log('Новый маршрут:', pathname);
});
return <div>Мое приложение</div>;
}
4. Создание кастомного хука для отслеживания
// hooks/useNavigationTracking.ts
import { usePathname, useSearchParams } from 'next/navigation';
import { useEffect, useRef } from 'react';
interface NavigationEvent {
path: string;
query: Record<string, string>;
timestamp: number;
referrer?: string;
}
export function useNavigationTracking(onNavigate?: (event: NavigationEvent) => void) {
const pathname = usePathname();
const searchParams = useSearchParams();
const previousPathRef = useRef<string>();
useEffect(() => {
// Создаём событие только если путь действительно изменился
if (previousPathRef.current !== pathname) {
const event: NavigationEvent = {
path: pathname,
query: Object.fromEntries(searchParams),
timestamp: Date.now(),
referrer: previousPathRef.current,
};
console.log('Navigation event:', event);
// Пользовательский коллбэк
onNavigate?.(event);
// Отправка в аналитику
sendAnalytics('page_view', {
page_path: pathname,
page_title: document.title,
referrer: previousPathRef.current,
});
previousPathRef.current = pathname;
}
}, [pathname, searchParams, onNavigate]);
}
// Использование в компоненте
export function Page() {
useNavigationTracking((event) => {
console.log(`Пользователь перешёл с ${event.referrer} на ${event.path}`);
});
return <div>Содержимое страницы</div>;
}
5. Отслеживание с сохранением истории
// hooks/useNavigationHistory.ts
import { usePathname } from 'next/navigation';
import { useEffect, useState } from 'react';
export function useNavigationHistory() {
const pathname = usePathname();
const [history, setHistory] = useState<string[]>([pathname]);
useEffect(() => {
setHistory((prev) => {
// Не добавляем дубликаты
if (prev[prev.length - 1] === pathname) {
return prev;
}
return [...prev, pathname];
});
}, [pathname]);
return {
history,
current: pathname,
previous: history[history.length - 2],
canGoBack: history.length > 1,
};
}
// Использование
function MyComponent() {
const { current, previous, canGoBack, history } = useNavigationHistory();
return (
<div>
<p>Текущая страница: {current}</p>
<p>Предыдущая страница: {previous}</p>
<p>История: {history.join(' -> ')}</p>
<button disabled={!canGoBack}>Назад</button>
</div>
);
}
6. Обработка guard'ов маршрутов
// lib/routeGuards.ts
import { useRouter, usePathname } from 'next/navigation';
import { useEffect } from 'react';
import { useAuth } from '@/hooks/useAuth';
export function useProtectedRoute(requiredRole?: string) {
const router = useRouter();
const pathname = usePathname();
const { user, isLoading } = useAuth();
useEffect(() => {
if (isLoading) return;
// Если нет пользователя - редирект на login
if (!user) {
router.push(`/login?redirect=${pathname}`);
return;
}
// Если нужна конкретная роль
if (requiredRole && user.role !== requiredRole) {
router.push('/forbidden');
return;
}
}, [user, isLoading, router, pathname, requiredRole]);
return { isAuthorized: !!user && (!requiredRole || user.role === requiredRole) };
}
// Использование в защищённой странице
function AdminPanel() {
const { isAuthorized } = useProtectedRoute('admin');
if (!isAuthorized) {
return <div>Загрузка...</div>;
}
return <div>Админ панель</div>;
}
7. Интеграция с аналитикой
// lib/analytics.ts
export function usePageAnalytics() {
const pathname = usePathname();
const searchParams = useSearchParams();
useEffect(() => {
// Google Analytics
if (typeof window !== 'undefined' && window.gtag) {
window.gtag('config', 'GA_MEASUREMENT_ID', {
page_path: pathname,
page_title: document.title,
});
}
// Yandex Metrica
if (typeof window !== 'undefined' && window.ym) {
window.ym(YANDEX_ID, 'hit', pathname);
}
// Sentry для отслеживания ошибок
import('@sentry/nextjs').then(({ captureMessage }) => {
captureMessage(`Navigation to ${pathname}`);
});
}, [pathname, searchParams]);
}
Лучшие практики
- Используй встроенные инструменты - usePathname в Next.js вместо History API
- Логируй изменения маршрутов - для отладки и аналитики
- Обрабатывай guard'ы маршрутов - перенаправляй неавторизованных пользователей
- Интегрируй с аналитикой - отслеживай поведение пользователей
- Сохраняй state - когда нужно вернуться на предыдущую страницу с одинаковым состоянием
Правильное отслеживание маршрутов - это основа для хорошей аналитики и UX.