Implementing Redux in Next.js (App Router): A Comprehensive Guide
Tags: nextjs, redux, react, javascript
Published: 2024-04-16 06:17:30

## Introduction
Next.js, a popular React framework, offers flexibility and control over state management. However, it lacks a built-in solution. This is where **Redux/Zustand** comes in.

Redux and Zustand are both state management solutions for Next.js apps, but they differ in complexity and approach. Redux is more complex but offers predictability, scalability, and a larger community. Zustand is simpler but less predictable and scalable, with a smaller community. Choose Redux for large and complex applications requiring strict data flow and a well-defined architecture. Choose Zustand for smaller applications where simplicity and performance are essential. The best choice depends on your project's specific needs and preferences.

This article is more focused on **Redux** than Zustand.

## Prerequisites

1. react
2. redux
3. redux-toolkit
4. redux-persist


## Folder Structure
I am keeping redux codes in `/store` folder and creating a wrapper(Provider) component in `StoreProvider.tsx` file.

```
/app
  layout.tsx
  page.tsx
  StoreProvider.tsx
/store
  store.ts
  hooks.ts
  storage.ts
  storage
  /actions
   Actions.ts
 /reducers
   Reducers.ts
   RootReducers.ts
```
I am assuming that you are already have basic knowledge on _Redux_, _Actions_, _Reducers_, _combineReducers_. I am also using _redux-persist_ for persistence of the data.

in `/store/storage.ts`:
```javascript
import createWebStorage from "redux-persist/lib/storage/createWebStorage";


const createNoopStorage = () => {
  return {
    getItem(_key: any) {
      return Promise.resolve(null);
    },
    setItem(_key: any, value: any) {
      return Promise.resolve(value);
    },
    removeItem(_key: any) {
      return Promise.resolve();
    },
  };
};

const storage = typeof window !== "undefined" ? createWebStorage("local") : createNoopStorage();

export default storage;
```
The storage function specifies the storage engine to be used for persisting the state(localStorage in web).

in `store/store.ts`:
```javascript
import { configureStore } from "@reduxjs/toolkit";
import { persistReducer } from "redux-persist";
import storage from "./storage";
import RootReducer from "./reducers/RootReducers";

const persistConfig = {
  key: "root",
  storage,
  blacklist: ["tracking"],
};

const persistedReducer = persistReducer(persistConfig, RootReducer)

export const makeStore = () => {
  return configureStore({
    reducer: persistedReducer,
    devTools: process.env.NODE_ENV !== "production",
    middleware: (getDefaultMiddleware) =>
      getDefaultMiddleware({
        serializableCheck: {
          ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
        },
      }),
  });
};
export type AppStore = ReturnType<typeof makeStore>;
export type RootState = ReturnType<AppStore["getState"]>;
export type AppDispatch = AppStore["dispatch"];
```
This function `makeStore` configures a Redux store with a persisted reducer, conditionally enables Redux DevTools based on the environment, and attempts to customize the middleware using **redux-thunk**.

To utilise the function(makeStore), we need to create a **client** component that will initiate redux store and share with application using the React-Router **Provider** component.

In `app/StoreProvider.tsx`:
```javascript
'use client'
import { useRef } from 'react'
import { Provider } from 'react-redux'
import { makeStore,AppStore } from '../store/store'
import { PersistGate } from "redux-persist/integration/react";
import { persistStore } from "redux-persist";

export default function StoreProvider({ children }: {
  children: React.ReactNode
}) {
  const storeRef = useRef<AppStore>()
  if (!storeRef.current) {
    storeRef.current = makeStore()
  }
  const persistor = persistStore(storeRef.current);

  return (
    <Provider store={storeRef.current}>
      <PersistGate loading={null} persistor={persistor}>
        {children}
      </PersistGate>
    </Provider>
  );
}
```
Then we will use this wrapper component in mainlayout in our application.

In `app/layout.tsx`:
```javascript
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
import StoreProvider from "./StoreProvider";

const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
  title: "Next-Redux",
  description: "Implementing Redux in Nextt(App Router)",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
   <html lang="en">
      <body className={inter.className}>
        <StoreProvider>
          {children}
        </StoreProvider>
      </body>
    </html>
 );
}
```

## Conclusion
The Redux state has been implemented with persistence in our Next.js application. _Redux-Persist_ effectively manages the application state and restores data even when the page refreshes or navigates. Additionally, _Thunk_ middleware allows you to write action creators that return a function instead of an action.
I hope this will helpful for implementing Redux in Next.js application. If you have any suggestions for improving the code, please leave them in the comments section. If you found this post helpful, please like and share it.

**Happy Coding!**