Edit: I'm also curious if this problem would exist with other state management solutions like Redux.
Imagine a large Next.js pages router website that is using context providers for state management. Now imagine you want to migrate that site to the new app router. Ideally, we'd like to do this incrementally, page-by-page. But the problem is, if you have two pages, one served by the pages router and one served by the app router, and they both share the same context, it seems they don't really share the same instance of the context. This makes sense since they each essentially have their own rendering tree.
Example file structure
--context
-ShoppingCartContext.js
--app
-page.js
--pages
--cart
-index.js
If app/page.js
and pages/cart/index.js
both use context/ShooppingCartContext.js
they will actually be using separate instances of that context. So items added to the cart on app/page.js
won't show up in the cart on pages/cart/index.js
.
This makes it difficult to incrementally migrate pages that share a context to the new app router without having to migrate all pages sharing any single context in a single commit. Is there any way to get around this?
Here is a sample Stackblitz demonstrating the problem.
The issue occurs because pages served by the pages
and app
routers maintain separate React trees, so they can't share the same instance of a context provider. This is expected since each rendering tree is isolated from the other, and it creates a problem when attempting to share state, like a shopping cart, between pages incrementally migrated to the app
router.
One solution would be to use Redux or Zustand as an external state management solution. These libraries store the state outside of the component tree.
Another option is to lift the context provider to a higher level, ensuring it wraps both the pages
and app
trees. You can define your context provider in a global location like _app.js
(which still works in the pages
directory) and ensure it wraps all components, including those served by the app
router. In this way, even though the trees are separate, the context provider will be shared at the root level, thus making it accessible for both the routers.
You could also consider persisting the shared state to a common storage medium like localStorage
or a server-side session, though this might involve more boilerplate code and is less optimal for real-time state updates.