Маршрутизация в веб: React Router и Express - гайд для новичков
Перейти

Маршрутизация в веб: React Router и Express – гайд для новичков

#Веб-разработка  #React  #Node.js  
Пройдите тест, узнайте какой профессии подходите
Сколько вам лет
0%
До 18
От 18 до 24
От 25 до 34
От 35 до 44
От 45 до 49
От 50 до 54
Больше 55

Для кого эта статья:

  • Разработчики, интересующиеся созданием веб-приложений с использованием React и Express
  • Студенты и начинающие программисты, желающие освоить маршрутизацию в веб-разработке
  • Профессионалы, ищущие советы по улучшению пользовательского опыта и архитектуры приложений

Представьте, что вы построили дом, но забыли сделать двери между комнатами. Именно так выглядит веб-приложение без маршрутизации — красивое, но абсолютно бесполезное. Маршрутизация — это система навигации, позволяющая пользователям перемещаться между различными частями вашего приложения. Без неё ваше React-приложение останется одностраничным в буквальном смысле, а Express-сервер не сможет обрабатывать запросы. Этот гайд расставит все точки над i и научит вас создавать эффективную маршрутизацию как на клиенте, так и на сервере. 🧭

Что такое маршрутизация в веб-приложениях и зачем она нужна

Маршрутизация — это механизм, который определяет, как приложение реагирует на клиентский запрос к определенному пути (URL). По сути, это своеобразная карта, которая сопоставляет URL-адреса с соответствующим контентом или функциональностью.

В веб-приложениях существует два основных типа маршрутизации:

  • Server-side routing (серверная маршрутизация) — традиционный подход, при котором каждый запрос к новому URL вызывает полную перезагрузку страницы с сервера.
  • Client-side routing (клиентская маршрутизация) — современный подход, характерный для SPA (Single Page Applications), когда переходы между "страницами" происходят без перезагрузки браузера, за счет JavaScript.
Характеристика Server-side routing Client-side routing
Перезагрузка страницы Полная перезагрузка при каждом переходе Без перезагрузки страницы
SEO-оптимизация Хорошая по умолчанию Требует дополнительных усилий
Пользовательский опыт Более медленный Более плавный и быстрый
Нагрузка На сервер На клиент (браузер)

Понимание маршрутизации критически важно для разработки современных веб-приложений по нескольким причинам:

  • Улучшение UX — пользователи ожидают, что могут использовать кнопки навигации браузера (назад/вперед) и делиться ссылками на конкретные "страницы" вашего приложения.
  • Организация кода — маршрутизация помогает структурировать приложение, разделяя его на логические компоненты.
  • Управление состоянием — правильная маршрутизация упрощает управление состоянием приложения, привязывая его к URL.
  • SEO-оптимизация — корректно настроенная маршрутизация улучшает индексацию вашего приложения поисковыми системами.

Максим Петров, ведущий фронтенд-разработчик Мой первый проект с React превратился в настоящий кошмар. Я создал впечатляющее SPA для интернет-магазина, но забыл должным образом настроить маршрутизацию. Результат? Пользователи не могли делиться ссылками на товары в социальных сетях, история браузера не работала, а при обновлении страницы клиенты возвращались на главную вместо просматриваемого продукта. После интеграции React Router пользовательский опыт кардинально улучшился. Конверсия выросла на 24%, а количество брошенных корзин снизилось на треть. Этот случай научил меня: никогда не недооценивайте важность правильной маршрутизации — это не просто техническая деталь, а ключевой элемент успешного веб-приложения.

Пошаговый план для смены профессии

Client-side маршрутизация с React Router DOM: первые шаги

React Router — это стандартная библиотека для маршрутизации в React-приложениях. Она позволяет реализовать навигацию между различными компонентами без перезагрузки страницы, обеспечивая при этом синхронизацию UI с URL. 🚀

Для начала необходимо установить библиотеку:

npm install react-router-dom

или

yarn add react-router-dom

Базовая настройка React Router включает несколько ключевых компонентов:

  • BrowserRouter — компонент-обертка, который должен охватывать все элементы вашего приложения, использующие маршрутизацию.
  • Routes — контейнер для определения маршрутов.
  • Route — определяет отдельный маршрут и компонент, который должен рендериться при его активации.
  • Link — создает навигационные ссылки, не вызывающие перезагрузку страницы.

Вот простой пример базовой настройки React Router:

JS
Скопировать код
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';

function App() {
return (
<BrowserRouter>
<nav>
<Link to="/">Главная</Link>
<Link to="/about">О нас</Link>
<Link to="/contact">Контакты</Link>
</nav>

<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/about" element={<AboutPage />} />
<Route path="/contact" element={<ContactPage />} />
</Routes>
</BrowserRouter>
);
}

Для организации вложенных маршрутов в React Router используется компонент Outlet:

JS
Скопировать код
import { BrowserRouter, Routes, Route, Link, Outlet } from 'react-router-dom';

function Layout() {
return (
<div>
<nav>
<Link to="/">Главная</Link>
<Link to="/blog">Блог</Link>
</nav>

<Outlet /> {/* Здесь будет отображаться содержимое вложенных маршрутов */}

<footer>© 2023 Моё приложение</footer>
</div>
);
}

function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<HomePage />} />
<Route path="blog" element={<BlogPage />} />
<Route path="blog/:postId" element={<BlogPost />} />
</Route>
</Routes>
</BrowserRouter>
);
}

React Router предлагает и другие типы маршрутизаторов помимо BrowserRouter:

Тип роутера Применение URL формат
BrowserRouter Стандартные веб-приложения /path/to/page
HashRouter Статический хостинг без настройки сервера /#/path/to/page
MemoryRouter Тестирование, React Native В памяти (не в URL)
StaticRouter Серверный рендеринг Определяется сервером

Дополнительные полезные хуки из react-router-dom:

  • useNavigate — программная навигация (например, после отправки формы)
  • useParams — доступ к параметрам URL
  • useLocation — информация о текущем URL
  • useSearchParams — работа с query-параметрами

Server-side маршрутизация в Express: настройка базовых роутов

Express — популярный фреймворк для Node.js, который упрощает разработку серверных приложений. Его система маршрутизации позволяет определить, как приложение отвечает на клиентские запросы по конкретным маршрутам (эндпоинтам) и HTTP-методам. 🔧

Для начала работы с Express необходимо установить его через npm:

npm install express

Базовый пример маршрутизации в Express выглядит так:

JS
Скопировать код
const express = require('express');
const app = express();
const port = 3000;

// Базовый маршрут
app.get('/', (req, res) => {
res.send('Главная страница');
});

// Маршрут с параметрами
app.get('/users/:userId', (req, res) => {
res.send(`Информация о пользователе ${req.params.userId}`);
});

// Обработка POST-запроса
app.post('/api/data', (req, res) => {
res.json({ message: 'Данные получены' });
});

// Запуск сервера
app.listen(port, () => {
console.log(`Сервер запущен на порту ${port}`);
});

Express поддерживает различные HTTP-методы, соответствующие операциям CRUD:

  • GET — получение данных (app.get())
  • POST — создание новых данных (app.post())
  • PUT — полное обновление существующих данных (app.put())
  • PATCH — частичное обновление существующих данных (app.patch())
  • DELETE — удаление данных (app.delete())

Для более организованной структуры кода в Express рекомендуется использовать Router:

JS
Скопировать код
// userRoutes.js
const express = require('express');
const router = express.Router();

// Маршруты будут доступны по /users/...
router.get('/', (req, res) => {
res.send('Список пользователей');
});

router.get('/:id', (req, res) => {
res.send(`Пользователь ${req.params.id}`);
});

router.post('/', (req, res) => {
res.send('Пользователь создан');
});

module.exports = router;

// app.js
const express = require('express');
const userRoutes = require('./userRoutes');
const app = express();

// Все маршруты из userRoutes будут с префиксом /users
app.use('/users', userRoutes);

app.listen(3000);

Важным аспектом маршрутизации в Express является обработка промежуточного ПО (middleware) — функций, которые имеют доступ к объекту запроса (req), объекту ответа (res) и следующей middleware-функции в цикле:

JS
Скопировать код
// Middleware для логирования запросов
app.use((req, res, next) => {
console.log(`${req.method} ${req.url}`);
next(); // Передаем управление следующему middleware
});

// Middleware для определенного маршрута
app.get('/protected', 
(req, res, next) => {
// Проверка авторизации
if (!req.headers.authorization) {
return res.status(401).send('Требуется авторизация');
}
next();
}, 
(req, res) => {
res.send('Защищенный контент');
}
);

Для обработки ошибок в Express используются специальные middleware с четырьмя параметрами:

JS
Скопировать код
// Обработчик ошибок
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('Что-то пошло не так!');
});

Алексей Сидоров, архитектор серверных решений Однажды нам пришлось оптимизировать высоконагруженный API для сервиса доставки еды. Главная проблема заключалась в неэффективной маршрутизации: каждый запрос проходил через десятки ненужных middleware, а структура роутов напоминала запутанный лабиринт. Первым делом мы реорганизовали маршруты, сгруппировав их по доменным областям с помощью Express Router. Затем внедрили условную загрузку middleware: проверка авторизации выполнялась только для защищенных маршрутов, а тяжелая валидация данных — только для POST и PUT запросов. Результат превзошел ожидания: время отклика API сократилось на 67%, а серверная нагрузка снизилась настолько, что мы смогли уменьшить количество инстансов вдвое. Этот опыт показал, как важно уделять внимание архитектуре маршрутизации даже в, казалось бы, простых Express-приложениях.

Взаимодействие React Router и Express в полноценном приложении

Создание современного веб-приложения часто требует взаимодействия между клиентской маршрутизацией (React Router) и серверной маршрутизацией (Express). Понимание того, как они работают вместе, критически важно для разработки надежных приложений. 🔄

Существует несколько подходов к интеграции React с Express:

  1. Раздельные приложения — React-приложение работает на клиенте и взаимодействует с Express API через HTTP-запросы
  2. Серверный рендеринг (SSR) — Express рендерит React-компоненты на сервере и отправляет готовый HTML клиенту
  3. Гибридный подход — комбинация API-эндпоинтов и серверного рендеринга для оптимальной производительности и SEO

Рассмотрим типичную структуру проекта с раздельными клиентской и серверной частями:

my-app/
├── client/ # React-приложение
│ ├── public/
│ ├── src/
│ │ ├── components/
│ │ ├── pages/
│ │ ├── App.js # Здесь настройка React Router
│ │ └── index.js
│ └── package.json
├── server/ # Express-сервер
│ ├── routes/
│ │ ├── api.js # API маршруты
│ │ └── auth.js # Маршруты авторизации
│ ├── index.js # Основной файл сервера
│ └── package.json
└── package.json # Корневой package.json для скриптов

В файле server/index.js необходимо настроить Express для обработки API-запросов и перенаправления всех остальных запросов на React-приложение:

JS
Скопировать код
const express = require('express');
const path = require('path');
const apiRoutes = require('./routes/api');
const authRoutes = require('./routes/auth');

const app = express();
const PORT = process.env.PORT || 5000;

// Парсинг JSON-данных
app.use(express.json());

// API маршруты
app.use('/api', apiRoutes);
app.use('/auth', authRoutes);

// Для продакшн-режима: обслуживание статических файлов React
if (process.env.NODE_ENV === 'production') {
// Статические файлы
app.use(express.static(path.join(__dirname, '../client/build')));

// Все остальные GET-запросы направляем на React
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, '../client/build/index.html'));
});
}

app.listen(PORT, () => {
console.log(`Сервер запущен на порту ${PORT}`);
});

На стороне клиента (в React-приложении) необходимо настроить обращение к API:

JS
Скопировать код
// В React-компоненте
import { useState, useEffect } from 'react';

function UserList() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);

useEffect(() => {
// Обращение к API
fetch('/api/users')
.then(response => response.json())
.then(data => {
setUsers(data);
setLoading(false);
})
.catch(error => {
console.error('Error fetching users:', error);
setLoading(false);
});
}, []);

if (loading) return <div>Загрузка...</div>;

return (
<div>
<h1>Пользователи</h1>
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
);
}

export default UserList;

Чтобы упростить разработку, можно настроить прокси в package.json React-приложения:

json
Скопировать код
// client/package.json
{
"name": "client",
"version": "0.1.0",
"private": true,
"dependencies": {
// ...
},
"proxy": "http://localhost:5000"
}

Это позволит делать относительные запросы к API (например, fetch('/api/users')) во время разработки, которые будут автоматически перенаправляться на сервер Express.

При настройке авторизации между React и Express важно правильно организовать управление сессиями:

  • Использование JWT (JSON Web Tokens) для аутентификации
  • Хранение токена в localStorage или в httpOnly cookie
  • Создание защищенных маршрутов в React с использованием компонента-обертки
  • Проверка токена на сервере перед доступом к защищенным API

Продвинутые техники маршрутизации: параметры, редиректы и защита

Освоив основы маршрутизации в React и Express, пора перейти к более продвинутым техникам, которые позволят сделать ваше приложение гибким, безопасным и удобным для пользователей. 🔒

Работа с параметрами URL

В React Router параметры URL определяются с помощью синтаксиса с двоеточием и извлекаются с помощью хука useParams:

JS
Скопировать код
// Определение маршрута с параметром
<Route path="/products/:productId" element={<ProductDetail />} />

// Использование параметра в компоненте
import { useParams } from 'react-router-dom';

function ProductDetail() {
const { productId } = useParams();

// Теперь можно использовать productId для загрузки данных
return <div>Детали продукта {productId}</div>;
}

В Express параметры URL определяются аналогично и доступны через req.params:

JS
Скопировать код
app.get('/api/products/:productId', (req, res) => {
const { productId } = req.params;

// Получение данных продукта из базы данных
// ...

res.json(product);
});

Работа с query-параметрами

Query-параметры удобны для фильтрации, сортировки и пагинации. В React Router для работы с ними используется хук useSearchParams:

JS
Скопировать код
import { useSearchParams } from 'react-router-dom';

function ProductList() {
const [searchParams, setSearchParams] = useSearchParams();
const category = searchParams.get('category');
const page = searchParams.get('page') || 1;

// Фильтрация по категории
// ...

return (
<div>
<h1>Продукты{category ? ` в категории ${category}` : ''}</h1>
{/* Список продуктов */}

<button onClick={() => setSearchParams({ page: Number(page) + 1 })}>
Следующая страница
</button>
</div>
);
}

В Express query-параметры доступны через req.query:

JS
Скопировать код
app.get('/api/products', (req, res) => {
const { category, page = 1, limit = 10 } = req.query;
const skip = (page – 1) * limit;

// Запрос к базе данных с фильтрацией и пагинацией
// ...

res.json({
products,
pagination: {
page: Number(page),
totalPages,
hasMore: page < totalPages
}
});
});

Редиректы и программная навигация

Часто требуется программно перенаправлять пользователя, например, после успешной авторизации. В React Router это делается с помощью компонента Navigate или хука useNavigate:

JS
Скопировать код
// С использованием компонента
import { Navigate } from 'react-router-dom';

function PaymentSuccess() {
return <Navigate to="/order-confirmation" replace />;
}

// С использованием хука
import { useNavigate } from 'react-router-dom';

function LoginForm() {
const navigate = useNavigate();

const handleSubmit = async (event) => {
event.preventDefault();
// Логика авторизации
// ...

if (success) {
navigate('/dashboard', { state: { message: 'Успешный вход' } });
}
};

return (
<form onSubmit={handleSubmit}>
{/* Поля формы */}
</form>
);
}

В Express редиректы выполняются с помощью метода res.redirect:

JS
Скопировать код
app.post('/api/login', (req, res) => {
const { username, password } = req.body;

// Проверка учетных данных
// ...

if (success) {
// Создание токена
const token = generateToken(user);
res.cookie('jwt', token, { httpOnly: true });
return res.status(200).json({ success: true, user });
}

res.status(401).json({ success: false, message: 'Неверные учетные данные' });
});

Защищенные маршруты

Для защиты маршрутов в React можно создать компонент-обертку:

JS
Скопировать код
import { Navigate, useLocation } from 'react-router-dom';

function ProtectedRoute({ children }) {
const isAuthenticated = localStorage.getItem('token'); // Упрощенный пример
const location = useLocation();

if (!isAuthenticated) {
// Перенаправление на страницу входа с сохранением изначального маршрута
return <Navigate to="/login" state={{ from: location }} replace />;
}

return children;
}

// Использование в маршрутах
<Route 
path="/dashboard" 
element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
} 
/>

В Express защита маршрутов осуществляется с помощью middleware:

JS
Скопировать код
// middleware/auth.js
const jwt = require('jsonwebtoken');

function authMiddleware(req, res, next) {
// Получение токена из заголовков или куки
const token = req.cookies.jwt || req.header('Authorization')?.replace('Bearer ', '');

if (!token) {
return res.status(401).json({ message: 'Требуется авторизация' });
}

try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded; // Добавляем информацию о пользователе в объект запроса
next();
} catch (error) {
res.status(401).json({ message: 'Недействительный токен' });
}
}

// Использование в маршрутах
const auth = require('./middleware/auth');

app.get('/api/profile', auth, (req, res) => {
// req.user доступен благодаря middleware
// ...
res.json(user);
});

Обработка несуществующих маршрутов (404)

В React Router:

JS
Скопировать код
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
{/* Другие маршруты */}

{/* Должен быть последним */}
<Route path="*" element={<NotFound />} />
</Routes>

В Express:

JS
Скопировать код
// Обработка маршрутов API
app.use('/api', apiRoutes);

// Обслуживание статических файлов React
app.use(express.static(path.join(__dirname, '../client/build')));

// Для React Router (все остальные GET-запросы)
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, '../client/build/index.html'));
});

// Обработка 404 для всех остальных запросов (например, POST к несуществующему эндпоинту)
app.use((req, res) => {
res.status(404).json({ message: 'Не найдено' });
});

Маршрутизация не просто технический аспект — это фундамент пользовательского опыта. Сочетая клиент-серверные подходы, вы получаете мощный инструмент для создания интуитивно понятных, безопасных и динамичных приложений. React Router и Express представляют идеальный баланс между мгновенным отображением контента на клиенте и надежной обработкой данных на сервере. Начните с малого, изучите базовые концепции, а затем постепенно внедряйте продвинутые техники. Помните, что правильно спроектированная маршрутизация — залог масштабируемости вашего проекта в будущем.

Проверь как ты усвоил материалы статьи
Пройди тест и узнай насколько ты лучше других читателей
Какова основная функция маршрутизаторов в веб-разработке?
1 / 5

Ольга Шадрина

React-разработчик

Свежие материалы

Загрузка...