NextJS provides an out-of-the-box development experience for building apps complete with server side rendering, routing and performance optimizations. Integrating Redux raises a couple of questions:
- Where do we initialize the store?
- How do we define initial state?
- How do we use the store in components?
- How do we handle state when navigating?
This article aims to be a quick and dirty breakdown of the NextJS + Redux example here, answering these questions along the way.
It’s a good idea to make sure you understand the following concepts before trying to piece everything together:
- Redux fundamentals | https://redux.js.org/tutorials/essentials/part-1-overview-concepts
- NextJS Pages | https://nextjs.org/docs/basic-features/pages
- NextJS SSR, SSG | https://nextjs.org/docs/basic-features/data-fetching
- NextJS Routing | https://nextjs.org/docs/routing/introduction
The Redux example is bare bones and a great point to start your NextJS + Redux app. Get started by running
npx create-next-app --example with-redux my-app
This will set up the Redux example into the
my-app directory, located wherever you execute the command.
cd into the directory and run
npm install to finalize setup.
A NextJS page can be rendered both client and server side, meaning initializing the store needs to work for both cases. In the example this is handled by a function doing different things depending on whether it’s called from the client or the server:
- If rendered server side, it will always initialize a new store to avoid injecting user specific data into pre-rendered pages.
- If rendered client side, it will create a store if one doesn’t exist or return an existing store² (it could also return a new store with previously existing state³, this will be covered later).
In both cases, it’s called by a hook at the root of the application from a custom App component⁴. This ensures it’s called whenever a page renders, enabling us to
- Initialize the store with page specific state
- Pass the store to the rest of the application
- Handle navigating from page to page
Initial state in the example is defined as default, hardcoded state¹ and page specific initial state.
Page specific initial state is defined using NextJS’s data fetching methods, e.g.
getServerSideProps. With them we inject data that is appropriate for being fetched server side, such as auth state or data from requests requiring secrets.
Depending on your requirements, this can be done for pages rendered dynamically on each request² (SSR) or pre-rendered pages served from cache³(SSG).
If no initial page state is defined via NextJS’s data fetching methods, the app will default to initializing with the hardcoded state⁴.
Using the store in components
The hook covered earlier gives us direct access to the store — however, its function is limited to store initialization and as per the example it should only be called in the App component so that the store can be passed to the rest of the app via
react-redux ‘s provider¹.
The stores are then referenced using
useDispatch to access state and dispatch actions².
Navigating with state
When navigating from page to page it’s likely we want to preserve existing state while injecting page specific state served via NextJS’s data fetching methods. As routing is client side, it should be handled without refreshing the page.
The hook mentioned previously handles this, if our app sees the page specific initial state has changed when a page re-renders¹, it will merge the new state into the existing one and re-initialize the store².
Note that this approach will create a new instance of the store, meaning if you’re debugging the page with e.g. redux dev tools it will show the old store as unchanged and you will have to select the new store manually.
In conclusion, the NextJS + Redux example is a succinct introduction to an app that uses both libraries. Some potential improvements to it are:
- Redux toolkit usage: redux-toolkit is a library for simplifying your redux logic, be it providing good defaults for store initialization, cutting down on the boilerplate with state slices or introducing “mutable” updates to your state with the help of immer. Carefully introducing redux-toolkit into your app early could ensure it scales well into the future.
- Better page navigation state handling: the current example rebuilds the store whenever the initial state of the page changes. While this is functional, it does create a new instance of the store which complicates logic that depends on references to the original store, like debugging via tools a la redux-dev-tools. A better approach would hydrate the existing store with new state without modifying the reference to the store e.g. via an action.
This is my first article. Hopefully it was useful and things are a little clearer than they were when you first started reading :)