-
Notifications
You must be signed in to change notification settings - Fork 27.5k
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
App router: Content flickering with React.createContext and next/dynamic for SSG/SSR #64060
Comments
Can you try switching |
Hey @huozhi ! Thanks for the answer. 'use client';
import { lazy } from 'react';
export const ClientComponent = lazy(() => import('./ClientComponent'));
export const ClientComponent2 = lazy(() => import('./ClientComponent2'));
// Other client components imports However, I get this error (have never seen this kind of error before lol): I found the only way to fix it: remove Am I missing something? |
Hi @huozhi! I'm running into this issue as well, and also thought to try But I'm glad to hear that there's a potential fix, with removing the Suspense boundary when no loading state is provided! I believe this would solve the issue 👍. |
I can confirm the next/dynamic doesn't work even if done in a |
Moving aside the issue with React.ContextAPI breaking next/dynamic, there is one more “issue” with next/dynamic I want to show. We have a number of JAMStack apps built with Next.js, that we are currently moving from Pages router to App router. In CMS we have a content that describes a page -> sections, their order, props etc. Nothing fancy there, quite common approach. To map CMS content to actual components, we use this kind of registry (simplified): // registry.ts
const ClientComponentLvl1 = dynamic(() => import('./atoms/ClientComponentLvl1'));
const ClientComponentLvl2 = dynamic(() => import('./molecules/ClientComponentLvl2'));
const ClientComponentLvl3 = dynamic(() => import('./organisms/ClientComponentLvl3'));
const components = {
ClientComponentLvl1,
ClientComponentLvl2,
ClientComponentLvl3
}
export const getComponent = (name: keyof typeof components) => components[name]
// page.tsx
import {getComponent} from "@/components/registry";
const getPageProps = async () => {
const props = await fetch(`content from CMS`);
return props;
}
export default async function Page() {
const props = await getPageProps();
const Section = getComponent(props.sectionName);
return (
<main>
<Section />
</main>
);
} All pages are built as SSG. If we open some page, HTML loads as it supposed to, we immediately see the content, no CLS. The problem appears if we NAVIGATE to this page using built-in Opened directly (looks good): direct.movInternal navigation (CLS): navigated.movSo again: we can directly open the SSG generated page and see the content, but we CAN’T navigate to this page through the app without seeing how it dynamically loads. And this applies only to the ‘use client’ components. RSC is good. |
### What Removing the Suspense boundary on top of `next/dynamic` by default, make it as `React.lazy` component with preloading CSS feature. ### Why Extra Suspense boundary is causing extra useless rendering. For SSR, it shouldn't render `loading` by default Related: #64060 Related: #64687 Closes [NEXT-3074](https://linear.app/vercel/issue/NEXT-3074/app-router-content-flickering-with-reactcreatecontext-and-nextdynamic) This is sort of a breaking change, since removing the Suspense boundary on top of `next/dynamic` by default. If there's error happening in side the dynamic component you need to wrap an extra Suspense boundary on top of it
In the repro seems there's a setState call in React context Provider when on mount, which will trigger the re-render of Suspense boundary in |
Hi! The real-world use case is loading some initial data from localStorage and setting it as a state on mount. We need it, unfortunately. Please take a look at my previous comment as well. The fix you had initially pushed, fixed both of our issues (I tested it on a canary version). Both flickering on mount and flickering on redirect. I know you had the reason to revert it, I just want to stress out that we can't finish our migration to the App router in a number of JAMStack apps. Using "registry with dynamic imports" approach proved well on Pages router and we want to keep it in the App router. |
@huozhi Removing of extra suspense boundary fixed the loading state to flash before rendering the next/dynamic - #64687
💯 💯 💯 |
Hi @snehakhobragade90 the setState in provider was the original reproduction of this issue. In your repro there's a setState call above the
|
@KirillEvtushenko I patched an improvement in #65486 that will preload the chunks earlier, and seemed to work for me with the initial reproduction I downloaded before. Could you help verify with latest canary again to see if it helps since I don't have the source code of the demo from the video? Also regarding to the read of localStorage, is that possible to finish in callback of |
@huozhi, that's correct. Calling I have a sandbox that demonstrates the |
@huozhi, tested on As for redirect to the page using Link component, dynamic loading is still consequent: Screen.Recording.2024-05-11.at.11.56.26.mov |
@KirillEvtushenko could you share the codebase of this video to let us dig into it? 🙏 |
Of course! Updated the repo and readme. |
@huozhi quick proposal, thoughts if we want to add the extra suspense as a conditional element ? We can probably use the suspense prop (which I see was deprecated, or we can introduce a new prop to handle this logic) |
One more repro case to pile on here. When the initial fix PR was reverted, I dug into the history of |
Might be related to this issues - #65796 The flickering can be seen by doing the below steps, -
Edit(12 Jun 2024): This got fixed in https://github.com/vercel/next.js/releases/tag/v14.2.4 |
@huozhi are there any updates on this ? We are completely blocked to launch our Nextjs project due to the lack of treeshaking support coupled with these unresolved issues. Any updates would be helpful to plan next steps at our end. Thanks |
I'm looking into this now with previous approach, but there's still errors could throw on client that breaks the dom insertion. We're still investigating |
### What Removing the Suspense boundary on top of `next/dynamic` by default, make it as `React.lazy` component with preloading CSS feature. ### Why Extra Suspense boundary is causing extra useless rendering. For SSR, it shouldn't render `loading` by default Related: #64060 Related: #64687 Closes [NEXT-3074](https://linear.app/vercel/issue/NEXT-3074/app-router-content-flickering-with-reactcreatecontext-and-nextdynamic) This is sort of a breaking change, since removing the Suspense boundary on top of `next/dynamic` by default. If there's error happening in side the dynamic component you need to wrap an extra Suspense boundary on top of it
### What Removing the Suspense boundary on top of `next/dynamic` by default, make it as `React.lazy` component with preloading CSS feature. ### Why Extra Suspense boundary is causing extra useless rendering. For SSR, it shouldn't render `loading` by default Related: #64060 Related: #64687 Closes [NEXT-3074](https://linear.app/vercel/issue/NEXT-3074/app-router-content-flickering-with-reactcreatecontext-and-nextdynamic) This is sort of a breaking change, since removing the Suspense boundary on top of `next/dynamic` by default. If there's error happening in side the dynamic component you need to wrap an extra Suspense boundary on top of it
### What Reland #64716 Removing the Suspense boundary on top of `next/dynamic` by default, make it as `React.lazy` component with preloading CSS feature. * Remove `suspense` option in `next/dynamic` since it's already deprecated for a while * Remove the default loading in app router implmentation of `next/dynamic` ### Why Extra Suspense boundary is causing extra useless rendering. For SSR, it shouldn't render `loading` by default Related: #64060 Related: #64687 Closes [NEXT-3074](https://linear.app/vercel/issue/NEXT-3074/app-router-content-flickering-with-reactcreatecontext-and-nextdynamic) This is sort of a breaking change, since removing the Suspense boundary on top of `next/dynamic` by default. If there's error happening in side the dynamic component you need to wrap an extra Suspense boundary on top of it
We've landed #67014 on canary as the fix, please test it out 🙏 The repro works well locally for me. I'll close this for now |
This closed issue has been automatically locked because it had no new activity for 2 weeks. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you. |
Link to the code that reproduces this issue
https://codesandbox.io/p/devbox/interesting-aryabhata-ckyy6f
To Reproduce
Current vs. Expected behavior
When using
next/dynamic
with React.createContext, I expect that it will work the same way as without React.createContext.Please pay attention that initial request with html correctly contains all the needed html. Provider's rerender (recreating of the
value
object prop) then causes that flickering.issue.mov
Provide environment information
Operating System: Platform: darwin Arch: arm64 Version: Darwin Kernel Version 23.4.0: Wed Feb 21 21:44:43 PST 2024; root:xnu-10063.101.15~2/RELEASE_ARM64_T6000 Binaries: Node: 20.10.0 npm: 10.2.3 Yarn: 1.22.18 pnpm: 8.6.5 Relevant Packages: next: 14.1.4 eslint-config-next: 13.5.4 react: 18.2.0 react-dom: 18.2.0 typescript: 5.4.3 Next.js Config: output: N/A
Which area(s) are affected? (Select all that apply)
App Router, Dynamic imports (next/dynamic)
Which stage(s) are affected? (Select all that apply)
next dev (local), next build (local), Vercel (Deployed), Other (Deployed)
Additional context
No response
NEXT-3074
The text was updated successfully, but these errors were encountered: