If you’re working with the Next.js App Router and using the dynamic()
function for component-level code splitting, you may have encountered an annoying issue: flickering during rendering of a conditionally-rendered dynamic component. Unfortunately, this is a known issue with the App Router and the dynamic
function in Next.js. This behavior can degrade user experience, so solving the Next.js dynamic flicker on your website is crucial.
In this post, I’ll break down:
- Why
next/dynamic
causes flickering - Why it’s worse with nested dynamic components
- When
dynamic()
is still safe to use - A practical alternative using
React.lazy()
andSuspense
- What trade-offs to expect when switching
The Flickering Problem in Next.js
Using dynamic()
from next/dynamic
is a great way to lazy-load components and reduce your JavaScript bundle size. It also supports options like { ssr: false }
to only load components on the client side.
However, when you use these components with the App Router, they often cause a flash of missing or unstyled content, especially during fast navigation or when conditionally rendering dynamic components.
Nested dynamic()
calls tend to amplify this issue. For example, a parent component conditionally loading a child via dynamic()
, which in turn loads another sub-component dynamically, can make the flickering more severe.
This issue has been reported in GitHub issues and community threads, but a rock-solid fix hasn’t yet made it into the framework.
Interestingly, this flicker seems to affect nested dynamic components more than top-level ones. In my testing, first-level dynamically rendered components used directly in the page file rarely exhibit the issue, which means it’s generally safe to use next/dynamic
there to avoid flash of unstyled content (FOUC) during initial mount.
The Better Alternative: React.lazy()
+ Suspense
One workaround that has proven effective is switching from next/dynamic
to native React.lazy()
with Suspense
. This approach introduces fewer hydration inconsistencies and minimizes flickering, even with nested lazy-loaded components.
Use next/dynamic
for components initially rendered on the page, and use React.lazy()
for nested components that are rendered conditionally inside those components.
Example 1: Top-level safe usage with next/dynamic
import dynamic from 'next/dynamic';
import { isSignedInAsync } from '../auth';
const PageShell = dynamic(() => import('../components/PageShell'));
export default async function Home() {
const isSignedIn = await isSignedInAsync();
if (isSignedIn) return null;
return <PageShell />;
}
In this example, PageShell
is conditionally rendered on the server using dynamic components. This is safe since the dynamic component is rendered with the initial HTML from the server.
Example 2: Nesting with React.lazy()
and Suspense
"use client";
import dynamic from 'next/dynamic';
const NestedComponent = dynamic(() => import('./NestedComponent'));
export default function PageShell() {
const [showNested, setShowNested] = useState(false);
return (
<div>
<h1>Welcome</h1>
<button onClick={() => setShowNested(true)}>Load Nested Component</button>
{showNested && (
<Suspense fallback={<div>Loading nested...</div>}>
<NestedComponent />
</Suspense>
)}
</div>
);
}
We can safely use React.lazy()
and Suspense
inside our dynamically-rendered PageShell
component to conditionally render our NestedComponent
, and still benefit from lazy-loading and code-splitting.
If we try using the dynamic
function instead of React.lazy
here, we may get the Next.js dynamic flicker.
Trade-offs of Using React.lazy()
Instead of dynamic
While React.lazy()
and Suspense
often result in smoother rendering, there are two notable downsides:
1. No Server-Side Rendering
Unlike next/dynamic
, which lets you disable or enable SSR, React.lazy()
only supports client-side rendering. This might hurt SEO if your component needs to be visible to crawlers.
2. Flash of Unstyled Content (FOUC) on Mount
If you do try to use React.lazy()
for SSR and use it in the server-rendered HTML, React.lazy()
may cause a brief flash of unstyled content because the Next.js bundler doesn’t automatically include the styles for components loaded through React.lazy()
in the server-rendered HTML. This limitation can lead to inconsistent rendering.
This is why it’s best to use next/dynamic
for components that are visible in the server-rendered HTML, ensuring that styles and structure are present at first paint, while reserving React.lazy()
for non-critical or nested components. Using next/dynamic
in the initial server-rendered HTML does not seem to cause flickering.
Final Thoughts on Preventing the Next.js Dynamic Flicker
If you’re seeing flickering with next/dynamic
and conditional rendering, especially in complex nested layouts, you’re not alone. While the Next.js team continues to evolve App Router, switching to React.lazy()
and Suspense
where you can may provide a smoother user experience at this time.
To summarize:
- Use
next/dynamic
safely for top-level page components - Use
React.lazy()
for nested dynamic imports to reduce flicker
Leave a Reply