The problem I'm having is hydrating the user data from local storage on app reload/page refresh.
In my project I am using NextJS for frontend, and for support libraries I am using redux-toolkit for redux management across the application and next-redux-wrapper for state hydration for the wrapped pages.
The user can log in, and in that case I store the isLoggedIn boolean in local storage and in the redux state. Depending on the isLoggedIn boolean value I change the Navbar component styles (the Navbar is included directly in _app.tsx).
When the user refreshes any page the isLoggedIn boolean is not loaded into the state but is present in local storage.
In the past I have been using redux-persist but I have opted out of using it because the PersistGate was blocking the UI from rendering until the persisted data is fetched from storage which conflicts with the idea of SSR.
Currently I have the isLoggedIn loading problem fixed by using the App.getInitialProps method in _app.ts which then results in hydration from next-redux-persist being called for each and every page loaded, but this introduces another problem: all pages are now server side rendered and there is no NextJS' static page optimisation.
Is there any way to not lose static page optimisation from NextJS, not use the redux-persist library and still be able to hydrate the client side store when any page is refreshed?
Current code structure (some code is omitted for simplicity):
file: _app.tsx
import { wrapper } from 'store';
const MyApp = ({ Component, pageProps }: AppProps) => {
return (
<>
<Navbar />
<Component {...pageProps} />
</>
);
};
MyApp.getInitialProps = async (appContext) => {
const appProps = await App.getInitialProps(appContext);
return { ...appProps };
};
export default wrapper.withRedux(MyApp);
file: store.ts
import {
combineReducers,
configureStore,
EnhancedStore,
getDefaultMiddleware
} from '@reduxjs/toolkit';
import { createWrapper, MakeStore } from 'next-redux-wrapper';
import userReducer from 'lib/slices/userSlice';
const rootReducer = combineReducers({
user: userReducer
});
const setupStore = (context): EnhancedStore => {
const middleware = [...getDefaultMiddleware(), thunkMiddleware];
if (process.env.NODE_ENV === 'development') {
middleware.push(logger);
}
return configureStore({
reducer: rootReducer,
middleware,
// preloadedState,
devTools: process.env.NODE_ENV === 'development'
});
};
const makeStore: MakeStore = (context) => setupStore(context);
export const wrapper = createWrapper(makeStore, {
debug: process.env.NODE_ENV === 'development'
});
file: userSlice.ts
import { createSlice } from '@reduxjs/toolkit';
import { HYDRATE } from 'next-redux-wrapper';
const initialState = {
isLoggedIn: false
}
export const userSlice = createSlice({
name: 'user',
initialState,
reducers: {
login: (state) => {
state.isLoggedIn = true;
localStorage.setItem('loggedInData', { isLoggedIn: true });
}
},
extraReducers: (builder) => {
builder
.addCase(HYDRATE, (state, action: any) => {
if (typeof window !== 'undefined') {
const storedLoggedInData = localStorage.getItem('loggedInData');
if (storedLoggedInData != null && storedLoggedInData) {
const parsedJson = JSON.parse(storedLoggedInData);
state.isLoggedIn = parsedJson.isLoggedIn ?? false;
} else {
state.isLoggedIn = false
}
}
});
}
});
export const isLoggedInSelector = (state: RootState) => state.user.isLoggedIn;
export default userSlice.reducer;
file: Navbar.tsx
import { useSelector } from 'react-redux';
import { isLoggedInSelector } from 'lib/slices/userSlice';
export default function Navbar() {
const isLoggedIn = useSelector(isLoggedInSelector);
return (
<div className={`${ isLoggedIn ? 'logged-in-style' : 'logged-out-style'}`}>...</div>
)
}