-
Notifications
You must be signed in to change notification settings - Fork 27.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Context provided in _app.js can't be consumed in pages in SSR #4194
Comments
Is it because _app.js provides its own context? 🤔 |
That actually can't be the problem when you nest inside |
It's related to |
@timneutkens as the bundling seems to work for the client, does that mean we could split the server bundles in a similar way to the client? |
Unfortunately it doesn't work as expected with commonschunk on the server. I have some ideas to fix this once and for all, but haven't had time to implement it yet. |
Will this be fixed? This problem break lots of behavior from module's local variable, and I think it's a important issue. |
Well it's not really a Next.js issue. More so with webpack, so I need to dig into this pretty deep. If anyone has experience with it please do reach out. |
@timneutkens i'm not super familiar but i'm willing to do a deep dive to help figure out a solution. Is there any pointers / additional details you could share to help me get started, i'm gonna get started researching the issue on my own but any knowledge transfer would be helpful. |
So, since Next 5 we run webpack 2 times, once for the server, once for the client. On the server webpack will pack every page into it's own bundle ( Say you have 2 pages that both import Because of this module state / singletons don't work, since if you server render page 1, and it holds a module state inside the component, for example: let timesRendered = 0
export default () => {
timesRendered++
return <div>test</div>
} It won't shared between server rendering of 2 pages. From what I've understood looking at the new context API, this is exactly what the context API depends on (local module state). So if you have I hope this explains the issue. I'm not entirely sure how we can fix this issue. In Next 4 and below we emitted every possible file into |
@timneutkens thanks for this writeup. i'm digging in now and might have additional questions for you as I work through it. |
Is it possible to create a similar "main" commons chunk for the server bundles? This issue doesn't seem like a problem for the client bundles ( |
@joeporpeglia @timneutkens -> I used dynamic to import the context provider in the app and the consumer in the page to test how that modified the bundle. As expected the code related to the consumer initialization was put into its own chunk in .next/dist but the problem isn't solved. I gave my context consumer a obnoxious name to help with searching the dist files. import React, { Component, createContext } from 'react';
const BradContext = createContext({ x: 3 });
export class BradProvider extends Component {
state = {
x: 5,
};
render() {
return (
<BradContext.Provider value={this.state}>
{this.props.children}
</BradContext.Provider>
)
}
}
export const BradConsumer = BradContext.Consumer; The state is kept entirely within this file. In _app.js import App, { Container } from 'next/app';
import dynamic from 'next/dynamic';
const BradProvider = dynamic(import('../components/context').then(r => r.BradProvider));
export default class MyApp extends App {
render() {
const { Component, pageProps } = this.props;
return (
<Container>
<BradProvider>
<Component {...pageProps} />
</BradProvider>
</Container>
)
}
} in index.js: import React, { Component } from 'react';
import dynamic from 'next/dynamic';
const BradConsumer = dynamic(import('../components/context').then(r => r.BradConsumer));
export default class Test extends Component {
render() {
return (
<BradConsumer>
{value => <span>{value.x}</span>}
</BradConsumer>
)
}
} With code splitting and module state, if the module is entirely self contained within a chunk does it solve the problem you were outlining @timneutkens -- i can confirm that the only time the state for the context is referenced within the compiled code is in the chunk and not within the page or the app computed entry points. Or is it the case that both times it is imported from the various entry points that the module state is essentially initialized again so that even though the code is split out each entry point is creating its own in-memory allocation for the code contained in the common chunk? |
With the above implementation the server renders "3", on the client there is a flash of "loading" followed by "5". |
@timneutkens is the client side bundle just in .next/bundle versus server side in .next/dist/bundle? I'm trying to see how each of these differ in terms of its implementation of the component tree as it relates to the context (specifically the dynamic import variation I did above) and they seem identical. Its as if when the Consumer is being mounted it doesn't see the Provider in the same tree. |
Yeah this is the root problem here. For the server compilation only, both Unfortunately I don't think there's any way to create a commons chunk and have it load properly when targeting node. Maybe another solution could involve bundling Edit to show how that might look: Instead of importing app, document, and page as separate modules, we could destructure all three from a single page bundle: let { Page, Document, App } = await requirePage(page, {dir, dist}); |
If there were a way to build a commons chunk look like in my example? Would it exist in the chunks/ folder inside dist? And how would it differ from the one generated by using dynamic imports |
@brad-decker I don't believe there's any way to make commons chunks work when targeting node 😞. I also don't think the dynamic imports you provided in that example would work as long as the server renderer |
Sorry to disturb, but why _app is a thing? I have own app component, and context is preserved correctly. What am I missing? |
If you render that custom app component as the root in each of your |
For now i've worked around this issue by avoiding _app.js and wrapping every single page like so: // lib/page.js
import * as React from "react";
import urlContext from "./urlContext";
export default (PageComponent) => {
return class Page extends React.Component {
public static async getInitialProps(initialProps) {
const { req } = initialProps
const url = req && req.url;
let props = { url };
const getInitialProps = PageComponent.getInitialProps;
if (getInitialProps) {
props = {
...props,
...getInitialProps(initialProps)
}
}
return props;
}
public render() {
const url = this.props.url;
return (
<urlContext.Provider value={url}>
<PageComponent/>
</urlContext.Provider>
);
}
}
}
// pages/index.js
import page from 'lib/page.js'
export default page(() => {
return (
<div>A page</div>
)
}) |
@mtford90 yeah, i created a HoC as well for setting up context, but its not clean. I actually refactored away from a HoC when i upgraded to 6 and saw the potential there for the app to handle that for us. |
here is a quick repro with the current behaviour in case could be useful for debug/ try stuff |
@RustyDev that's something entirely different. What this does is pass
|
@joeporpeglia @timneutkens I ran into this last night and spent a couple hours banging my head on the wall. @mtford90 thanks for suggesting the workaround. Is there any proposal that could resolve this issue? |
Hi, any updates on this issue? |
I'm not entirely sure this is fixed, the shared module part, yes, but not the context behavior. I just upgraded to Compared to the browser, When Next.js renders, I have a simple case as the follows (yet I haven't tested exactly this example):
In the browser, When Next.js renders, edit: I have just tested a small example similar to the one listed above and that one works. The only obvious difference between this simple one and the piece of code I have which doesn't work, is that the latter is an external Node module that's pre-transpiled into edit 2: well... false alarm... so I figured out the issue. A Hidden element of Material UI (https://github.com/mui-org/material-ui/blob/master/packages/material-ui/src/Hidden/Hidden.js) had snuck its way into the code. What wasn't obvious, was that the implementation apparently defaults to a JavaScript version which would never render its children. I'd expect it to default to CSS - apparently not. |
@timneutkens Thank you for jumping on this so quickly! |
@gaearon happy to! was an interesting one to solve 😄 |
Fixes issue with `React.createContext()` per vercel/next.js#4194
@timneutkens With this fix, is Next version: 7.0.0 |
Just chiming in here. I'm currently experiencing this issue on I've created a file which creates a context:
I then import it in If I then use a However, if I import the context and use the Consumer in the index for instance, it only works client side. Now I'm wondering, should this be fixed on 6.1.2 or only in 7? |
Only in 7 |
I'm running into this same issue with Next 7.0.2. I found that if I wrap the context into its own component which won't update its state if the value passed as a property is undefined, that will work between pre-fetched page changes, however that has its own issue of propagating values (for some reason, the inner consumer stays one value behind). Here's a gist of my solution. https://gist.github.com/dfoverdx/4afe0e4ad4bda6702b9debf63ca6cd9c The one disadvantage to this approach is that it wraps the app's Container in an extra component per Context. If you have several contexts, the tree in the React debugger gets very wide. |
@timneutkens Also ran into this issue with Next 7.0.2. |
This issue persist for me in Next 8.1.0. I'm following the official "with redux" example here and I continue to see
My package.json includes "dependencies": {
"@zeit/next-sass": "^1.0.1",
"@zeit/next-typescript": "^1.1.1",
"next": "^8.1.0",
"node-sass": "^4.12.0",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-redux": "^7.0.3",
"redux": "^4.0.1"
} This only happens after adding |
I can also confirm i am having problems with context api in SSR on 8.1.0 and i had to downgrade again. |
Next 9.0.5 I am still NOT able to migrate from React SPA to Nextjs, with the React Context API. And also, the error message is misleading. I have the error message like this:
It took me a week to figure it out that it's NOT the router. It's the Context!!!! I hope you people at least fix the error message. The router is fine. |
@medmin please read this thread: https://twitter.com/timneutkens/status/1154351052973625346 This bug does not exist anymore so it must be something in your code, we have tests verifying it works correctly. |
@sytona I'm not sure what you're referring to. |
That's not true because webpack has it's own implementation of the module singleton system. This implementation does have the issue that the modules object is per-entrypoint, however I wrote a webpack plugin that changes it to share the modules object between entrypoints that solves this issue. |
Getting same issue in next 9.2.1. Context API only works if consumer is placed in _app.tsx. It does't work when placed in pages. |
This comment is not actionable and on a closed issue from almost 2 years ago. |
@mtford90 What does the |
It's still happening in Next.js 11... |
I can confirm I am seeing this exact problem in both v10 and v11. app.jsx wraps the pages with their contexts:
The ages get their contexts loaded just fine via client rendering/navigation with useRouter. If there is a SSRedirect or a user enters a url directly, the state that is supposed to be available from the context is "NULL". |
@hazae41 @markhaslam please create a new issue with a complete reproduction so that it can be investigated. The initially reported issue has tests in the Next.js test suite. |
React v16.3 context provided in
pages/_app.js
can be consumed and rendered in pages on the client, but is undefined in SSR. This causes React SSR markup mismatch errors.Note that context can be universally provided/consumed within
pages/_app.js
, the issue is specifically when providing context inpages/_app.js
and consuming it in a page such aspages/index.js
.Expected Behavior
Context provided in
pages/_app.js
should be consumable in pages both on the server for SSR and when browser rendering.Current Behavior
Context provided in
pages/_app.js
is undefined when consumed in pages for SSR. It can only be consumed for client rendering.Steps to Reproduce (for bugs)
In
pages/_app.js
:In
pages/index.js
:In
context.js
:Will result in:
Context
A large motivation for the
pages/_app.js
feature is to be able to provide context persistently available across pages. It's unfortunate the current implementation does not support this basic use case.I'm attempting to isomorphically provide the cookie in context so that
graphql-react
<Query />
components can get the user's access token to make GraphQL API requests. This approach used to work with separately decorated pages.Your Environment
The text was updated successfully, but these errors were encountered: