Управление заголовками страниц (document.title) — важная часть клиентской маршрутизации. В одностраничных приложениях (SPA) изменение URL не приводит к перезагрузке страницы, поэтому обновление заголовка должно выполняться вручную при каждом переходе по маршруту.
В Navigo маршруты описываются через обработчики, внутри которых можно напрямую изменять заголовок:
import Navigo from 'navigo';
const router = new Navigo('/');
router
.on({
'/': () => {
document.title = 'Главная';
},
'/about': () => {
document.title = 'О нас';
},
'/contact': () => {
document.title = 'Контакты';
}
})
.resolve();
Каждый обработчик отвечает не только за отображение контента, но и за установку соответствующего заголовка.
При увеличении количества маршрутов дублирование логики становится проблемой. Более масштабируемый подход — вынести заголовки в отдельную структуру:
const routes = {
'/': {
title: 'Главная',
handler: () => {
// логика отображения
}
},
'/about': {
title: 'О нас',
handler: () => {}
}
};
Инициализация маршрутов:
Object.keys(routes).forEach(path => {
router.on(path, () => {
document.title = routes[path].title;
routes[path].handler();
});
});
Преимущества:
Часто требуется формировать заголовок на основе параметров маршрута:
router.on('/user/:id', ({ data }) => {
const userId = data.id;
document.title = `Пользователь #${userId}`;
});
В более сложных случаях заголовок зависит от данных, полученных с сервера:
router.on('/post/:id', async ({ data }) => {
const response = await fetch(`/api/posts/${data.id}`);
const post = await response.json();
document.title = post.title;
});
Для унификации логики можно реализовать собственный слой обработки:
function withTitle(title, handler) {
return (...args) => {
document.title = title;
handler(...args);
};
}
router.on({
'/': withTitle('Главная', () => {}),
'/about': withTitle('О нас', () => {})
});
Такой подход:
Navigo позволяет использовать общий обработчик, который выполняется при каждом переходе:
router.hooks({
after: (match) => {
if (match.route && match.route.name) {
document.title = match.route.name;
}
}
});
Маршруты с именами:
router.on({
'/': { uses: () => {}, as: 'Главная' },
'/about': { uses: () => {}, as: 'О нас' }
});
Здесь свойство as используется как источник
заголовка.
Для поддержания единообразия используется шаблон:
function setTitle(pageTitle) {
const base = 'Моё приложение';
document.title = pageTitle ? `${pageTitle} — ${base}` : base;
}
Использование:
router.on('/about', () => {
setTitle('О нас');
});
Это обеспечивает:
Проблема: при асинхронной загрузке данных заголовок может устанавливаться с задержкой.
Решение — установка временного заголовка:
router.on('/post/:id', async ({ data }) => {
document.title = 'Загрузка...';
const response = await fetch(`/api/posts/${data.id}`);
const post = await response.json();
document.title = post.title;
});
При ошибках маршрутизации важно корректно обновлять заголовок:
router.notFound(() => {
document.title = 'Страница не найдена';
});
Или при ошибке загрузки данных:
router.on('/post/:id', async ({ data }) => {
try {
const response = await fetch(`/api/posts/${data.id}`);
if (!response.ok) throw new Error();
const post = await response.json();
document.title = post.title;
} catch {
document.title = 'Ошибка загрузки';
}
});
Помимо document.title, часто требуется обновлять
мета-теги:
function setMeta(name, content) {
let tag = document.querySelector(`meta[name="${name}"]`);
if (!tag) {
tag = document.createElement('meta');
tag.setAttribute('name', name);
document.head.appendChild(tag);
}
tag.setAttribute('content', content);
}
Использование:
router.on('/about', () => {
document.title = 'О нас';
setMeta('description', 'Информация о компании');
});
В больших приложениях применяется модульная структура:
// titleManager.js
export function updateTitle(route) {
const titles = {
home: 'Главная',
about: 'О нас'
};
document.title = titles[route] || 'Приложение';
}
// router.js
import { updateTitle } from './titleManager';
router.on({
'/': () => updateTitle('home'),
'/about': () => updateTitle('about')
});
Создание универсального обработчика:
function createRoute({ title, action }) {
return async (params) => {
document.title = 'Загрузка...';
const result = await action(params);
document.title = title(result);
};
}
Пример использования:
router.on('/post/:id', createRoute({
title: (post) => post.title,
action: async ({ data }) => {
const res = await fetch(`/api/posts/${data.id}`);
return res.json();
}
}));
Такой подход: