Loader'ы на сервере

Loader — это асинхронная функция, которая выполняется перед рендерингом маршрута и предназначена для получения данных, необходимых компоненту. В TanStack Router она играет ключевую роль при организации серверного рендеринга (SSR) и предварительной загрузки данных на сервере, обеспечивая согласованное состояние приложения до его передачи клиенту.


Основные принципы работы loader’ов

Loader вызывается при навигации на маршрут и может возвращать данные в любом виде: объект, массив, примитив. Возвращаемое значение передается в компонент через пропсы или через контекст маршрута. TanStack Router автоматически управляет асинхронностью, ожидая завершения loader’а перед рендерингом маршрута.

Пример структуры loader:

import { createRouter, createRoute } from '@tanstack/router'

const userRoute = createRoute({
  path: '/user/:id',
  loader: async ({ params, context }) => {
    const response = await fetch(`https://api.example.com/users/${params.id}`)
    if (!response.ok) throw new Error('Не удалось загрузить пользователя')
    return response.json()
  },
  component: UserPage
})

В этом примере loader получает объект params, содержащий параметры маршрута, и объект context, который может использоваться для передачи глобальных зависимостей, например, токена авторизации или клиента API.


Использование loader’ов на сервере

На сервере loader’ы выполняются перед рендерингом HTML. Это позволяет:

  • Предварительно загрузить данные и вставить их в разметку.
  • Уменьшить количество лишних запросов на клиенте.
  • Обеспечить SEO-дружественный рендеринг.

Пример серверного рендеринга с использованием loader’ов:

import express from 'express'
import { renderToString } from 'react-dom/server'
import { RouterProvider } from '@tanstack/router'
import { router } from './router'

const app = express()

app.get('*', async (req, res) => {
  try {
    const match = await router.match(req.url)
    
    // Выполнение loader'ов всех маршрутов, соответствующих текущему пути
    const data = await match.loaders()

    const html = renderToString(
      <RouterProvider router={router} initialData={data} />
    )

    res.send(`<!DOCTYPE html>
      <html>
        <head><title>SSR с TanStack Router</title></head>
        <body>
          <div id="root">${html}</div>
        </body>
      </html>`)
  } catch (err) {
    res.status(500).send('Ошибка сервера')
  }
})

Здесь метод match.loaders() выполняет все loader’ы, относящиеся к маршрутам, которые соответствуют текущему URL. Возвращаемые данные можно передать клиенту через initialData для повторного использования без лишних запросов.


Контекст и зависимости в loader’ах

Loader’ы могут получать контекст приложения. Обычно это объект с доступом к API, базе данных, сервисам аутентификации и другим глобальным зависимостям. Такой подход позволяет сделать loader полностью изолированным от компонента и облегчает тестирование.

const router = createRouter({
  context: {
    apiClient: new ApiClient(process.env.API_TOKEN)
  }
})

const profileRoute = createRoute({
  path: '/profile',
  loader: async ({ context }) => {
    return await context.apiClient.getProfile()
  },
  component: ProfilePage
})

Обработка ошибок и состояния загрузки

Loader’ы могут выбрасывать ошибки, которые TanStack Router позволяет перехватывать через специальные маршруты ошибок (errorElement) или глобальный обработчик. Также можно использовать состояния загрузки (pending) для отображения индикаторов ожидания.

const userRoute = createRoute({
  path: '/user/:id',
  loader: async ({ params }) => {
    const response = await fetch(`https://api.example.com/users/${params.id}`)
    if (!response.ok) throw new Response('Пользователь не найден', { status: 404 })
    return response.json()
  },
  component: UserPage,
  errorElement: UserErrorPage
})

При возникновении ошибки роутер автоматически отобразит errorElement, передав ему объект ошибки для детальной обработки.


Преимущества использования loader’ов на сервере

  • Согласованное состояние приложения: данные загружаются до рендеринга страницы, исключая «мерцание» пустых компонентов на клиенте.
  • Снижение нагрузки на клиент: минимизация дополнительных HTTP-запросов после загрузки страницы.
  • Улучшенное SEO: поисковые роботы получают полностью сгенерированную страницу с данными.
  • Упрощение управления асинхронностью: TanStack Router автоматически ждёт выполнения всех loader’ов, обеспечивая единый поток данных.

Советы по эффективному использованию loader’ов

  1. Разделение данных по маршрутам: каждый маршрут отвечает только за свои данные. Это повышает переиспользуемость и читаемость кода.
  2. Кеширование на сервере: результаты loader’ов можно кешировать, чтобы ускорить повторные запросы.
  3. Обработка ошибок в loader’ах: всегда учитывать возможность неуспешного запроса и использовать корректные статус-коды.
  4. Передача контекста: избегать прямого импорта глобальных сервисов в loader, лучше использовать context для независимости и тестируемости.

Loader’ы в TanStack Router обеспечивают мощный механизм для асинхронного получения данных как на клиенте, так и на сервере, позволяя строить масштабируемые приложения с серверным рендерингом и согласованным состоянием.