← Назад к вопросам

Как отследить изменение маршрута на странице?

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]);
}

Лучшие практики

  1. Используй встроенные инструменты - usePathname в Next.js вместо History API
  2. Логируй изменения маршрутов - для отладки и аналитики
  3. Обрабатывай guard'ы маршрутов - перенаправляй неавторизованных пользователей
  4. Интегрируй с аналитикой - отслеживай поведение пользователей
  5. Сохраняй state - когда нужно вернуться на предыдущую страницу с одинаковым состоянием

Правильное отслеживание маршрутов - это основа для хорошей аналитики и UX.