Создание роутера на сервере

В Vue Router существует возможность настройки маршрутов не только на клиенте, но и на сервере, что критично для приложений с серверным рендерингом (SSR). Серверный роутинг позволяет управлять навигацией на стороне сервера до того, как HTML будет отправлен пользователю, обеспечивая правильную генерацию контента для поисковых систем и ускоряя первый рендер страницы.


Импорт и создание роутера

Для создания роутера на сервере используется та же структура, что и на клиенте, с небольшими отличиями. Основные шаги включают импорт createRouter и createMemoryHistory:

import { createRouter, createMemoryHistory } from 'vue-router';
import Home from './pages/Home.vue';
import About from './pages/About.vue';

export function createAppRouter() {
  return createRouter({
    history: createMemoryHistory(),
    routes: [
      { path: '/', component: Home },
      { path: '/about', component: About },
    ],
  });
}

Ключевые моменты:

  • Используется createMemoryHistory() вместо createWebHistory(), так как на сервере нет объекта window.
  • Роутер создается как функция, чтобы при каждом запросе создавался отдельный экземпляр, предотвращая совместное состояние между запросами.

Интеграция с серверным рендерингом

При SSR важно дождаться разрешения всех асинхронных данных перед рендерингом страницы. Для этого Vue Router предоставляет метод router.isReady():

import { createSSRApp } from 'vue';
import { createAppRouter } from './router';

export function createApp() {
  const app = createSSRApp(App);
  const router = createAppRouter();
  app.use(router);

  return { app, router };
}

На сервере процесс выглядит так:

export async function render(url) {
  const { app, router } = createApp();
  router.push(url);
  await router.isReady();

  const html = await renderToString(app);
  return html;
}

Объяснение ключевых действий:

  • router.push(url) навигирует роутер на нужный путь перед рендерингом.
  • router.isReady() гарантирует, что все асинхронные компоненты и хук beforeRouteEnter завершены.
  • Каждый запрос получает новый экземпляр приложения и роутера, что предотвращает конфликты состояния между пользователями.

Асинхронные компоненты и загрузка данных

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

const About = defineAsyncComponent(() =>
  import('./pages/About.vue')
);

routes: [
  { path: '/', component: Home },
  { path: '/about', component: About, beforeEnter: async (to, from, next) => {
      await fetchDataForAbout();
      next();
  }},
]

Важно:

  • Любые асинхронные операции должны завершаться до рендеринга на сервере.
  • Использование beforeEnter позволяет серверу ожидать загрузку данных, исключая “мерцание” пустого состояния на клиенте.

Обработка ошибок маршрутизации

На сервере ошибки роутера должны корректно обрабатываться для возврата нужного HTTP-кода:

router.isReady().catch(err => {
  if (err.name === 'NavigationDuplicated') {
    // Игнорируем дублирующиеся навигации
  } else {
    throw err;
  }
});

Дополнительно для SSR удобно перехватывать маршруты, которые не совпадают ни с одним путем, чтобы возвращать 404:

router.beforeEach((to, from, next) => {
  if (!router.hasRoute(to.path)) {
    next('/404');
  } else {
    next();
  }
});

Сервисы и состояние при SSR

Для серверного рендеринга роутер часто интегрируется с глобальным состоянием (Vuex или Pinia) для предварительной инициализации данных:

const store = createStore();
router.beforeResolve(async (to) => {
  const matchedComponents = router.getMatchedComponents(to);
  for (const component of matchedComponents) {
    if (component.asyncData) {
      await component.asyncData(store);
    }
  }
});

Принцип работы:

  • Каждое совпавшее с маршрутом представление может иметь метод asyncData, который наполняет состояние перед рендерингом.
  • Это гарантирует, что HTML, отправленный с сервера, уже содержит все необходимые данные.

Резюме практических рекомендаций

  • Всегда использовать createMemoryHistory() для серверного роутера.
  • Создавать новый экземпляр роутера на каждый запрос.
  • Асинхронные данные и компоненты должны полностью загружаться до генерации HTML.
  • Обрабатывать маршруты с ошибками и несуществующие пути.
  • Интегрировать роутер с глобальным состоянием для синхронизации данных между сервером и клиентом.

Эти правила обеспечивают правильную работу Vue Router в SSR и позволяют создавать масштабируемые, быстрые и SEO-дружественные приложения.