Вопрос или проблема
У меня есть приложение на Next.js с Typescript, и я пытаюсь настроить redux с redux-persist. Если состояние в срезе такое же, как начальное состояние, приложение работает нормально, но если нужно сохранить некоторые состояния, я получаю ошибки гидратации:
Ошибка гидратации, потому что начальный интерфейс не соответствует тому, что было отрендерено на сервере.
Произошла ошибка при гидратации. HTML сервера был заменен контентом клиента в <#document>.
Ошибка гидратации, потому что начальный интерфейс не соответствует тому, что было отрендерено на сервере.
Также получаю эту ошибку на сервере:
Ошибка сохранения данных ReferenceError: window не определен
Хотя приложение работает исправно, даже когда возникают эти ошибки, я хотел бы их решить, чтобы выглядеть не так неаккуратно, но я довольно запутался, почему это происходит.
Вот мой сокращенный код для настройки redux:
slice.ts
import { InquiryCartState } from "@/types/InquiryCart";
import { createSlice } from "@reduxjs/toolkit";
const initialState: InquiryCartState = {
products: []
};
const inquiryCartSlice = createSlice({
name: "inquiryCart",
initialState,
reducers: {
},
});
export default inquiryCartSlice.reducer;
rootReducer.ts
import { combineReducers } from "@reduxjs/toolkit";
import inquiryCartReducer from "./slice";
import AsyncStorage from '@react-native-async-storage/async-storage';
import { persistReducer } from "redux-persist";
const persistConfig = {
key: "root",
storage: AsyncStorage,
};
export const rootReducer = combineReducers({
inquiryCart: persistReducer(persistConfig, inquiryCartReducer),
})
store.ts
import { configureStore } from "@reduxjs/toolkit";
import { rootReducer } from "./rootReducer";
export const store = configureStore({
reducer: rootReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({ serializableCheck: false })
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
ReduxProvider.ts
"use client"
import { Provider } from "react-redux";
import { ReactNode } from "react";
import { store } from "@/lib/store";
import { persistStore } from "redux-persist";
persistStore(store)
export default function ReduxProvider({ children }: { children: ReactNode }) {
return <Provider store={store}>{children}</Provider>;
}
Сервер не имеет доступа к вашим сохраненным данным, поэтому серверный рендер будет отображать то, что было бы отрендерено для только что созданного Redux магазина, в то время как ваш клиент будет рендерить в зависимости от сохраненных данных.
Нет способа сообщить серверу, что ваш пользователь сохранил, поэтому лучшее, что вы можете сделать, это либо жить с этим, либо отключить сохранение.
Ответ или решение
Ошибка гидратации в Next.js с Redux и redux-persist
Введение
При разработке приложений на базе Next.js, особенно с использованием Redux и redux-persist, разработчики иногда сталкиваются с ошибками гидратации. Эти ошибки возникают, когда серверная версия пользовательского интерфейса (UI) не совпадает с клиентской версией после рендеринга. Это может привести к неоднозначностям и потенциальным багам в поведении приложения. В данной статье мы рассмотрим причины возникновения таких ошибок и предложим решения для их устранения.
Проблема
По описанию, вы столкнулись с несколькими ошибками:
- Hydration failed because the initial UI does not match what was rendered on the server.
- An error occurred during hydration. The server HTML was replaced with client content in <#document>.
- Error storing data ReferenceError: window is not defined.
Также вы упомянули, что приложение работает как задумано, несмотря на наличие этих ошибок; однако для повышения качества пользовательского опыта имеет смысл решить их.
Причины возникновения ошибок
-
Разница в состоянии между сервером и клиентом: Когда сервер рендерит страницу, он использует начальное состояние Redux, заданное в вашем
initialState
. Однако, когда приложение загружается на клиенте, оно может извлечь данные изredux-persist
, которые могли измениться с момента последнего рендеринга. Эти различия приводят к конфликтам при гидратации. -
Доступ к объекту
window
на сервере: ОшибкаReferenceError: window is not defined
возникает из-за попытки доступа кwindow
на серверной стороне, где этот объект недоступен. Это часто случается, когда вы используетеAsyncStorage
, который предназначен для клиента.
Решения
С учетом вышесказанного, вот несколько подходов для решения проблем с гидратацией:
-
Управление состоянием и данными:
- Используйте начальное состояние, которое синхронизируется между сервером и клиентом. Например, вы можете использовать
getInitialProps
илиgetServerSideProps
для передачи состояния во время серверного рендеринга.
// Пример использования getServerSideProps export async function getServerSideProps() { const initialData = {}; // Получите начальные данные здесь return { props: { initialData } }; }
- Используйте начальное состояние, которое синхронизируется между сервером и клиентом. Например, вы можете использовать
-
Инициализация redux-persist только на клиенте:
- Оберните код, который использует
AsyncStorage
, в условие, проверяющее, существует лиwindow
. Это позволит избежать выполнения этого кода на сервере:
if (typeof window !== 'undefined') { persistStore(store); }
Это гарантирует, что код, связанный с
window
, будет выполняться только на клиенте, и ошибки не возникнут на серверной стороне. - Оберните код, который использует
-
Синхронизация инициализации состояния:
- Чтобы предотвратить несоответствие начального состояния, вы можете использовать пользовательские хуки или эффекты для загрузки данных после монтирования компонента. Это позволяет сначала запустить рендеринг с "статическим" состоянием, а затем синхронизировать его с тем, что было получено из persist.
Заключение
Ошибки гидратации в Next.js с Redux и redux-persist могут вызывать значительные проблемы, но их можно эффективно предотвратить и исправить. Основное внимание следует уделить синхронизации состояния между клиентом и сервером и избегать использования объектa window
на серверной стороне. Следуйте вышеуказанным рекомендациям, чтобы устранить возникшие ошибки и улучшить общее взаимодействие пользователя с вашим приложением.