Skip to content

Commit e100486

Browse files
jherrmarkerikson
authored andcommitted
One more read through and some additional sections
1 parent ce3619d commit e100486

File tree

1 file changed

+48
-19
lines changed

1 file changed

+48
-19
lines changed

docs/tutorials/nextjs.mdx

Lines changed: 48 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,16 @@ hide_title: true
2626

2727
## Introduction
2828

29-
NextJS is a server side rendering framework for React that presents some unique challenges for using Redux properly.
30-
These challenges include:
29+
NextJS is a server side rendering framework for React that presents some unique challenges for using Redux properly. These challenges include:
3130

3231
- **Per-request safe Redux store creation** - A NextJS server can handle multiple requests simultaneously. This means that the Redux store should be created per request and that the store should not be shared across requests.
33-
- **SSR-friendly store hydration** - NextJS applications are rendered twice, first on the server and again on the client. Failure to render the same page on both the client and the server will result in a "hydration error". So the Redux store will have to be initialized on the server and then re-initialized on the client with the same data.
34-
- **SPA routing support** - NextJS supports a hybrid model for client side routing. A customers first page load will get an SSR result from the server. Subsequent page loads will be handled by the client. This means that the Redux store should be preserved appropriately when navigating from route to route using NextJS's client side routing.
35-
- **Server caching friendly** - Recent versions of NextJS (specifically applications using the App Router architecture) support aggressive server caching. The ideal store artchiecture should support this caching.
32+
- **SSR-friendly store hydration** - NextJS applications are rendered twice, first on the server and again on the client. Failure to render the same page contents on both the client and the server will result in a "hydration error". So the Redux store will have to be initialized on the server and then re-initialized on the client with the same data in order to avoid hydration issues.
33+
- **SPA routing support** - NextJS supports a hybrid model for client side routing. A customer's first page load will get an SSR result from the server. Subsequent page navigation will be handled by the client. This means that with a singleton store defined in the layout, route-specific data will need to be selectively reset on route navigation, while non-route-specific data will need to be retained in the store.
34+
- **Server caching friendly** - Recent versions of NextJS (specifically applications using the App Router architecture) support aggressive server caching. The ideal store architecture should be compatible with this caching.
3635

3736
There are two architectures for a NextJS application; the Pages Router and the App Router. The Pages Router is the original architecture for NextJS.
3837

39-
Using Redux with the Pages Router is well understood and handled primarily by the (next-redux-wrapper)[https://github.com/kirill-konshin/next-redux-wrapper]. This tutorial will focus on the App Router architecture as it is the new default architecture option for NextJS and it presents some unique challenges for using Redux properly.
38+
Using Redux with the Pages Router is well understood and handled properly by the (next-redux-wrapper)[https://github.com/kirill-konshin/next-redux-wrapper]. This tutorial will focus on the App Router architecture as it is the new default architecture option for NextJS.
4039

4140
### How to Read This Tutorial
4241

@@ -46,14 +45,16 @@ This page assumes that you already have an exisiting NextJS application based on
4645

4746
The primary new feature of the NextJS App Router is the addition of support for React Server Components (RSCs). RSCs are a special type of React component that only renders on the server, as opposed to "client" components that render on **both** the client and the server. RSCs can be defined as `async` functions and return promises during rendering as they make async requests for data to render.
4847

49-
RSCs abilitiy to block for data means that with the App Router you no longer have `getServerSideProps` to fetch data for rendering. Any component in the tree can make asychronous requests for data. While this is very convenient it also means thats if you define global variables (like the Redux store) they will be shared across requests. This is a problem because the Redux store could be contaminated with data from other requests.
48+
RSCs abilitiy to block for data requests means that with the App Router you no longer have `getServerSideProps` to fetch data for rendering. Any component in the tree can make asychronous requests for data. While this is very convenient it also means thats if you define global variables (like the Redux store) they will be shared across requests. This is a problem because the Redux store could be contaminated with data from other requests.
5049

5150
Based on the architecture of the App Router we have these general recommendations for appropriate use of Redux:
5251

5352
- **No global stores** - Because the Redux store is shared across requests, it should not be defined as a global variable. Instead, the store should be created per request.
5453
- **RSCs should not read or write the Redux store** - RSCs cannot use hooks or context. They aren't meant to be stateful. Having an RSC read or write values from a global store violates the architecture of the NextJS App Router.
5554
- **The store should only contain mutable data** - We recommend that you use your Redux sparingly for data intended to be global and mutable.
5655

56+
These recommendations are specific to applications written with the NextJS App Router. Single Page Applications (SPAs) don't execute on the server and therefore can define stores as global variables. SPAs don't need to worry about RSCs since they don't exist in SPAs. And singleton stores can store whatever data you want.
57+
5758
### Creating a Redux Store per Request
5859

5960
Following along with the (Quick-Start guide)[./quick-start.mdx] we need to make some changes to the `app/store.js` file. The first change is to move from defining store as a global to defining a `makeStore` function that returns a new store for each request.
@@ -78,12 +79,6 @@ Now we have a function, `makeStore`, that we can use to create a store instance
7879
7980
To use this new `makeStore` function we need to create a new "client" component that will create the store and share it using the React-Redux `Provider` component.
8081
81-
:::tip Why Client Components?
82-
83-
Any component that interacts with the Redux store; creating it, providing it, reading from it, or writing to it, needs to be a client component because accessing the store requires React context and context is only available in client components.
84-
85-
:::
86-
8782
```ts title="src/app/StoreProvider.tsx"
8883
// file: app/store.ts noEmit
8984
import { configureStore } from '@reduxjs/toolkit'
@@ -127,7 +122,13 @@ export default function StoreProvider({
127122
}
128123
```
129124

130-
If you need to intialize the store with data from the parent component then define that data as a property and use an action on the slice to set the data in the store as shown below.
125+
:::tip Why Client Components?
126+
127+
Any component that interacts with the Redux store; creating it, providing it, reading from it, or writing to it, needs to be a client component because accessing the store requires React context and context is only available in client components.
128+
129+
:::
130+
131+
If you need to intialize the store with data from the parent component then define that data as a property on the client `StoreProvider` component and use a Redux action on the slice to set the data in the store as shown below.
131132

132133
```ts title="src/app/StoreProvider.tsx"
133134
// file: features/counter/counterSlice.ts noEmit
@@ -197,15 +198,15 @@ export default function StoreProvider({
197198
}
198199
```
199200

200-
In this example code we are insuring that this client component is re-render safe by checking the value of the reference. This component will only be rendered once per request on the server but might be rendered multiple times on the client if there are stateful client components located above this component in the tree, or if this component also contains other mutable state that causes a re-render.
201+
In this example code we are insuring that this client component is re-render safe by checking the value of the reference to ensure that the store is only created once. This component will only be rendered once per request on the server but might be re-rendered multiple times on the client if there are stateful client components located above this component in the tree, or if this component also contains other mutable state that causes a re-render.
201202

202-
The next step is to include the `StoreProvider` in your layout component. This will ensure that the store is created for each request and that the store is not shared across requests. In all client components further down the tree, you can use the store exactly as you would normally using the hooks provided by `react-redux`.
203+
The next step is to include the `StoreProvider` anywhere in the tree above where the store is used. You can locate the store in the layout component if all the routes using that layout need the store. Or if the store is only used in a specific route you can create and provide the store in that route handler. In all client components further down the tree, you can use the store exactly as you would normally using the hooks provided by `react-redux`.
203204

204205
### Per-route state
205206

206-
If you use NextJS's support for client side SPA-style navigation by using `next/navigation` then when customers navigating from page to page only the route component will be re-rendered. This means that if you have a Redux store created and provided in the layout component it will be preserved across route changes. This is not a problem if you are only using the store for global, mutable data. However, if you are using the store for per-route data then you will need to reset the store when the route changes.
207+
If you use NextJS's support for client side SPA-style navigation by using `next/navigation` then when customers navigate from page to page only the route component will be re-rendered. This means that if you have a Redux store created and provided in the layout component it will be preserved across route changes. This is not a problem if you are only using the store for global, mutable data. However, if you are using the store for per-route data then you will need to reset the route-specific data in the store when the route changes.
207208

208-
Shown below is a `ProductName` example component that uses the Redux store to store the mutable name of a product. The `ProductName` component part of a product detail route. In order to ensure that we have the correct name in the store we need to set the value in the store any time the `ProductName` component is initially rendered, which happens on any route change to the product detail route.
209+
Shown below is a `ProductName` example component that uses the Redux store to manage the mutable name of a product. The `ProductName` component part of a product detail route. In order to ensure that we have the correct name in the store we need to set the value in the store any time the `ProductName` component is initially rendered, which happens on any route change to the product detail route.
209210

210211
```ts title="src/app/ProductName.tsx"
211212
// file: features/product/productSlice.ts noEmit
@@ -287,10 +288,38 @@ export default function ProductName({ product }: { product: Product }) {
287288
}
288289
```
289290

290-
As we did we initializing the store on creation using dispatched actions we can also update the per-route store state to the new route state using dispatched actions. The `initialized` ref is used to ensure that the store is only initialized once per request.
291+
Here we are using the same intialization pattern as before, of dispatching actions to the store, to set the route-specific data. The `initialized` ref is used to ensure that the store is only initialized once per route change.
291292

292293
It is worth noting that initializing the store with a `useEffect` would not work because `useEffect` only runs on the client. This would result in hydration errors or flicker because the result from a server side render would not match the result from the client side render.
293294

295+
### Caching
296+
297+
The App Router has four seperate caches including `fetch` request and route caches. The most likely cache to cause issues is the route cache. If you have an application that accepts login you may have routes (e.g. the home route, `/`) that render different data based on the user you will need to disable the route cache by using the (`dynamic` export from the route handler)[https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamic]:
298+
299+
```ts
300+
export const dynamic = 'force-dynamic'
301+
```
302+
303+
After a mutation you should also invalidate the cache by calling (`revalidatePath`)[https://nextjs.org/docs/app/api-reference/functions/revalidatePath] or (`revalidateTag`)[https://nextjs.org/docs/app/api-reference/functions/revalidateTag] as appropriate.
304+
305+
### Redux Toolkit Query
306+
307+
We recommend using Redux Toolkit Query for data fetching **on the client only**. Data fetching on the server should use `fetch` requests from `async` RSCs.
308+
309+
You can learn more about Redux Toolkit Query in the [Redux Toolkit Query tutorial](https://redux-toolkit.js.org/tutorials/rtk-query).
310+
311+
### Checking Your Work
312+
313+
There are three key areas that you should check to ensure that you have set up Redux Toolkit correctly:
314+
315+
- **Server Side Rendering** - Check the HTML output of the server to ensure that the data in the Redux store is present in the server side rendered output.
316+
- **Route Change** - Navigate between pages on the same route as well as between different routes to ensure that route-specific data is initialized properly.
317+
- **Mutations** - Check that the store is compatible with the NextJS App Router caches by performing a mutation and then navigating away from the route and back to the original route to ensure that the data is updated.
318+
319+
### Overall Recommendations
320+
321+
The App Router presents a dramatically different archtecture for React applications from either the Pages Router or a SPA application. We recommend rethinking your approach to state mangement in the light of this new architecture. In SPA applications it's not unusual to have a large store that contains all the data, both mutable and immutable, required to drive the application. For App Router applications we recommend that you only use Redux for globally shared, mutable data. And that you use a combination of NextJS state (search params, route parameters, form state, etc.), React context and React hooks for all other state management.
322+
294323
## What You've Learned
295324

296325
That was a brief overview of how to set up and use Redux Toolkit with the App Router:

0 commit comments

Comments
 (0)