Redux in Server-Side Rendered Next.js: Merging Server and Client State

Serhii Koziy
4 min readJan 16, 2025

--

Next Redux

General overview

When building applications with Next.js, server-side rendering (SSR) is a powerful tool to improve SEO and reduce Time to First Paint (TTFP). However, managing state between the server and client can get tricky, especially when using Redux. This article explores how to set up Redux with server-side state in Next.js and seamlessly merge it with client-side state.

Why Manage State on Both the Server and Client?

In SSR, Next.js pre-renders the HTML with initial data fetched on the server. This data needs to be hydrated into the Redux store on the client to avoid mismatches and to allow users to interact with a fully functional app without re-fetching data unnecessarily.

Step 1: Set Up Redux in a Next.js App

First, ensure your Next.js project is set up with TypeScript. Install the required Redux packages:

npm install @reduxjs/toolkit react-redux

Create a basic Redux store.

store.ts:

import { configureStore, createSlice, PayloadAction } from '@reduxjs/toolkit';

// Example slice
const initialState = {
serverData: null as string | null,
};
const appSlice = createSlice({
name: 'app',
initialState,
reducers: {
setServerData: (state, action: PayloadAction<string>) => {
state.serverData = action.payload;
},
},
});
export const { setServerData } = appSlice.actions;
const store = configureStore({
reducer: {
app: appSlice.reducer,
},
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
export default store;

Step 2: Create a Redux Provider for Next.js

To use Redux throughout your app, wrap it with a provider. In Next.js, you can enhance this by creating an App wrapper.

_app.tsx:

import { Provider } from 'react-redux';
import store from '../store';

const App = ({ Component, pageProps }: any) => {
return (
<Provider store={store}>
<Component {...pageProps} />
</Provider>
);
};
export default App;

Step 3: Add Server-Side State with getServerSideProps

Fetch the initial data server-side in a page using getServerSideProps. Pass this data to the Redux store.

pages/index.tsx:

import { GetServerSideProps } from 'next';
import { useDispatch, useSelector } from 'react-redux';
import { setServerData } from '../store';
import { RootState } from '../store';

const Home = ({ initialServerData }: { initialServerData: string }) => {
const dispatch = useDispatch();
const serverData = useSelector((state: RootState) => state.app.serverData);
// Merge state only if Redux state is empty
if (!serverData) {
dispatch(setServerData(initialServerData));
}
return (
<div>
<h1>Redux SSR with Next.js</h1>
<p>Server Data: {serverData}</p>
</div>
);
};
// Fetch server-side data
export const getServerSideProps: GetServerSideProps = async () => {
// Simulating data fetching
const serverData = 'Hello from the server!';

return {
props: {
initialServerData: serverData,
},
};
};
export default Home;

Step 4: Persist State on the Frontend

When the app transitions from server-rendered to client-rendered, the Redux state should persist. However, the server-rendered state needs to merge with client-side state properly. To achieve this, Next.js passes the initial state via props, and the app uses these to populate the Redux store.

Hydration Hook (Optional Enhancement):

Create a reusable hook to hydrate the Redux store.

useHydrate.ts:

import { useEffect } from 'react';
import { useDispatch } from 'react-redux';

export const useHydrate = (initialState: any, setStateAction: any) => {
const dispatch = useDispatch();
useEffect(() => {
if (initialState) {
dispatch(setStateAction(initialState));
}
}, [dispatch, initialState, setStateAction]);
};

Update your component to use the hydration hook:

pages/index.tsx:

import { GetServerSideProps } from 'next';
import { useSelector } from 'react-redux';
import { setServerData } from '../store';
import { RootState } from '../store';
import { useHydrate } from '../hooks/useHydrate';

const Home = ({ initialServerData }: { initialServerData: string }) => {
useHydrate(initialServerData, setServerData);
const serverData = useSelector((state: RootState) => state.app.serverData);
return (
<div>
<h1>Redux SSR with Next.js</h1>
<p>Server Data: {serverData}</p>
</div>
);
};
export const getServerSideProps: GetServerSideProps = async () => {
const serverData = 'Hello from the server!';
return {
props: {
initialServerData: serverData,
},
};
};
export default Home;

Step 5: Debugging State Merging

To verify state merging, you can log the initial state and Redux store state during hydration.

useEffect(() => {
console.log('Initial Server State:', initialServerData);
console.log('Redux State:', serverData);
}, [initialServerData, serverData]);

Step 6: Advanced Usage — Combining Server and Client Data

If you need to fetch additional data client-side and merge it with the server state, extend the slice reducer to handle both server and client updates.

const appSlice = createSlice({
name: 'app',
initialState,
reducers: {
setServerData: (state, action: PayloadAction<string>) => {
state.serverData = action.payload;
},
appendClientData: (state, action: PayloadAction<string>) => {
state.serverData += ` | ${action.payload}`;
},
},
});

Dispatch actions client-side after hydration:

useEffect(() => {
dispatch(appendClientData('Client-side data!'));
}, [dispatch]);

Conclusion

Setting up Redux with server-side rendering in Next.js requires careful handling of initial state and hydration. By fetching state server-side, passing it via props, and merging it on the client, you ensure your app is performant, consistent, and user-friendly. With this setup, your app can seamlessly blend SSR’s power with Redux’s robust state management.

Happy coding! 🚀

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

No responses yet

Write a response