Предзагрузка данных на сервере (server prefetching) в RTK Query используется для формирования заранее заполненного кэша Redux-состояния до того, как приложение будет отрисовано на клиенте. Это особенно важно в сценариях SSR (Server-Side Rendering), где необходимо отдать пользователю уже готовую страницу с данными, а не пустой интерфейс с последующей загрузкой.
RTK Query предоставляет набор механизмов, которые позволяют извлекать данные на сервере и синхронизировать их с Redux store таким образом, чтобы клиентская часть приложения сразу работала с уже подготовленным кэшем.
RTK Query строит работу вокруг кэша запросов. Каждый endpoint описывает запрос, который может быть выполнен как на клиенте, так и на сервере. При серверной предзагрузке происходит следующее:
Ключевая особенность заключается в том, что RTK Query не делает различий между серверным и клиентским выполнением запроса — различие заключается только в окружении выполнения dispatch.
Каждый endpoint в RTK Query генерирует thunk initiate,
который может быть вызван напрямую через store.dispatch.
store.dispatch(api.endpoints.getUser.initiate(userId));
При серверной предзагрузке этот вызов инициирует запрос, а результат автоматически попадает в кэш.
После завершения всех необходимых запросов обычно требуется дождаться
их завершения через await.
await store.dispatch(api.endpoints.getUser.initiate(userId));
Если запрос уже существует в кэше, RTK Query может избежать
повторного сетевого вызова в зависимости от настроек
forceRefetch и времени keepUnusedDataFor.
После выполнения всех серверных запросов необходимо получить сериализованное состояние Redux store:
const preloadedState = store.getState();
Это состояние содержит:
Далее оно передаётся в клиентскую часть приложения как
initialState.
На клиенте создаётся store с использованием уже готового состояния:
const store = configureStore({
reducer: {
[api.reducerPath]: api.reducer,
},
preloadedState,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(api.middleware),
});
RTK Query автоматически распознаёт, что кэш уже существует, и использует его без повторных запросов.
При этом система сравнивает:
queryCacheKey;Если данные считаются свежими, повторный fetch не выполняется.
Помимо прямого initiate, RTK Query предоставляет утилиту
prefetch, которая предназначена для сценариев, где
необходимо заранее подготовить данные без немедленного использования
результата.
store.dispatch(api.util.prefetch('getUser', userId, { force: true }));
Поведение:
В отличие от initiate, prefetch не
возвращает результат запроса как основную цель, а ориентирована именно
на подготовку данных.
В серверных приложениях часто используется привязка предзагрузки к маршрутам. Каждый маршрут может определять набор данных, необходимых для рендера.
Пример логики:
/user/:id требует getUser;/posts/:id требует getPost и
комментарии;RTK Query в этом случае выступает как слой кэширования и выполнения запросов, а логика маршрутизации остаётся внешней.
RTK Query поддерживает одновременное выполнение нескольких thunk-операций:
await Promise.all([
store.dispatch(api.endpoints.getUser.initiate(userId)),
store.dispatch(api.endpoints.getPosts.initiate()),
]);
Особенности:
Promise.all.При повторной инициализации на сервере важно учитывать дублирование запросов. RTK Query предотвращает лишние вызовы через:
keepUnusedDataFor;Однако в SSR-контексте подписок может не быть, поэтому важную роль играет явное управление:
forceRefetch;RTK Query поддерживает tag-based invalidation, что сохраняется и в SSR-режиме.
Если endpoint использует providesTags:
getUser: builder.query({
query: (id) => `user/${id}`,
providesTags: (result, error, id) => [{ type: 'User', id }],
});
то серверный кэш может быть структурирован таким образом, что последующие предзагрузки автоматически учитывают изменения данных через:
dispatch(api.util.invalidateTags([{ type: 'User', id }]));
Это позволяет управлять актуальностью данных ещё до передачи состояния клиенту.
Хотя selectFromResult чаще применяется на клиенте для
оптимизации ререндеров, его логика косвенно влияет на SSR:
Таким образом, SSR создаёт «полную базу», а селекторы формируют «проекции» этой базы.
При серверной предзагрузке важно учитывать, что ошибки запросов также попадают в кэш:
isError сохраняется;error сериализуется;Это позволяет клиенту корректно отобразить состояние без повторного запроса.
Пример состояния:
status: 'rejected'error: { message, code }RTK Query не скрывает ошибки при SSR, что позволяет реализовывать полноценную серверную обработку.
Redux state, содержащий RTK Query cache, должен быть сериализуемым. Основные ограничения:
RTK Query по умолчанию соблюдает эти ограничения, но при кастомных трансформациях данных важно учитывать совместимость с JSON-сериализацией.
При больших приложениях предзагрузка может становиться дорогостоящей операцией. Основные стратегии оптимизации:
RTK Query эффективно работает в связке с частичной SSR-стратегией, где не все данные загружаются заранее, а только критические для первого рендера.
В сложных архитектурах поверх RTK Query строится отдельный слой:
RTK Query в этом случае остаётся источником истины для данных, а SSR слой — оркестратором запросов.
При повторной загрузке страницы клиентская часть:
Если состояние устарело, RTK Query автоматически инициирует refetch в
соответствии с политиками refetchOnMountOrArgChange.
Серверная предзагрузка снижает:
RTK Query делает этот процесс предсказуемым за счёт единого механизма кэширования, который одинаково работает на сервере и клиенте, устраняя необходимость дублирования логики получения данных.