DEV Community

Amit Kumar Rout
Amit Kumar Rout

Posted on • Updated on

Implementing Redux in Next.js (App Router): A Comprehensive Guide

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
Enter fullscreen mode Exit fullscreen mode

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:

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;
Enter fullscreen mode Exit fullscreen mode

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

in store/store.ts:

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"];
Enter fullscreen mode Exit fullscreen mode

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:

'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>
  );
}
Enter fullscreen mode Exit fullscreen mode

Then we will use this wrapper component in mainlayout in our application.

In app/layout.tsx:

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>
 );
}
Enter fullscreen mode Exit fullscreen mode

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!

Top comments (8)

Collapse
 
markerikson profile image
Mark Erikson

Hi, I'm a Redux maintainer. FYI there's a couple issues with that store setup logic:

  • You don't need to explicitly import or add redux-thunk. RTK's configureStore already adds the thunk middleware by default, so you never need to do that with RTK.
  • In fact, when you do middleware: () => new Tuple(thunk), you're also eliminating any of the default middleware, which will remove the dev-mode checks. Don't do that!
  • Additionally, if you're using redux-persist, you do need to do some additional customization of the middleware setup to avoid serialization warnings in dev mode.

Beyond that, we actually have a complete guide to setting up Redux Toolkit with Next.js in our docs.

Collapse
 
bladearya profile image
Amit Kumar Rout

Thank you for the information.

Collapse
 
ivanzusko profile image
Ivan Zusko

First of all, thank you for your work!

Regarding the guide itself:

  1. there is a bug: make the Quotes page as a default (basically import it instead of Count) and it won't display quotes. The request will be triggered, the data will be received, but the data won't be shown (Loading ... text will be visible)
  2. The code examples from the guide are quite substantially different from the code in the starter application
Collapse
 
simplenotezy profile image
Mattias Fjellvang

Hey Mark. Thanks for sharing the link to the Next.js guide on setting up Redux Toolkit with Next.js - however, how do we go about persisting data? Say I have an authSlice that contains a JWT token. Would you still recommend using redux-persist for the App Router as mentioned in this tutorial, or do you have any directions I should look?

Collapse
 
hugecarpenter123 profile image
hugecarpenter123

There is no documentation on redux-toolkit + redux-persist with NextJs. And setup using nextjs differs from react. Some official reccomendations with explanation would be appreciated.

Collapse
 
markerikson profile image
Mark Erikson

As I linked above, we have a specific docs page that details how to set up Redux Toolkit with Next:

We've never specifically documented "here's how to use redux-persist" at all, but that's because it's a separate library we don't maintain. There shouldn't be anything specific about that with Next that I know of, as it's just part of the store config process.

Collapse
 
mayank_tamrkar profile image
Mayank Tamrkar

good , but pls add how to use this or if it is better than add one example .

Collapse
 
robiulsagor profile image
Robiul Islam Sagar

__