Skip to content

Commit 7203d4f

Browse files
committed
feat: layout shift troubleshooting guide
1 parent 7b6e2bc commit 7203d4f

File tree

7 files changed

+186
-1
lines changed

7 files changed

+186
-1
lines changed

.DS_Store

0 Bytes
Binary file not shown.
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
---
2+
title: Investigating a layout shift
3+
description: Learn how to identify and diagnose layout shifts in your app
4+
---
5+
6+
## What is a layout shift?
7+
8+
Layout shift is a common issue in web development where elements on a page move unexpectedly,
9+
causing a poor user experience.
10+
11+
Here is an example of a layout shift:
12+
13+
<Frame>
14+
<video
15+
muted
16+
loop
17+
playsInline
18+
controls
19+
title="Layout shift example"
20+
className="w-full aspect-video"
21+
src="/images/developer/guides/troubleshooting/layout-shift/layout-shift.mp4"
22+
></video>
23+
</Frame>
24+
25+
Layout shifts can occur for a variety of reasons, such as:
26+
- Components re-rendering or remounting while page is loading
27+
- Images or videos loading without a specified size
28+
- Fonts loading and causing text to shift
29+
30+
## Reproducing a layout shift
31+
32+
Let's illustrate how layout shift can occur in a real-world scenario.
33+
34+
Imagine you have a Makeswift site and you want to use `next-auth` for authentication capabilities.
35+
So you add `SessionProvider` to manage the session state.
36+
37+
Since `SessionProvider` is a client component, you'd probably create a wrapper component, say `Providers`,
38+
to wrap your `SessionProvider` and use it in your `layout.tsx` file.
39+
40+
Here is the code you might end up with.
41+
42+
<CodeGroup>
43+
```tsx src/app/providers.tsx
44+
'use client'
45+
46+
import { SessionProvider } from 'next-auth/react'
47+
48+
export function Providers({ children }: { children: React.ReactNode }) {
49+
return <SessionProvider>{children}</SessionProvider>
50+
}
51+
```
52+
53+
```tsx src/app/layout.tsx
54+
// ...
55+
import { Providers } from './providers'
56+
57+
export default async function RootLayout({
58+
children,
59+
}: Readonly<{
60+
children: React.ReactNode
61+
}>) {
62+
return (
63+
<html lang="en">
64+
<head>
65+
<DraftModeScript />
66+
</head>
67+
<body className={clsx(inter.className, 'bg-gray-600')}>
68+
<MakeswiftProvider previewMode={(await draftMode()).isEnabled}>
69+
<Providers>
70+
<header className="flex items-center justify-between bg-gray-600 p-4 px-10 text-white">
71+
<h1 className="text-3xl font-bold underline">header</h1>
72+
</header>
73+
74+
{children}
75+
76+
<footer className="flex items-center justify-between bg-gray-600 p-4 px-10 text-white">
77+
<h1 className="text-3xl font-bold underline">footer</h1>
78+
</footer>
79+
</Providers>
80+
</MakeswiftProvider>
81+
</body>
82+
</html>
83+
)
84+
}
85+
```
86+
</CodeGroup>
87+
88+
Nothing seems wrong with this code, right? Now let's run the app and see what happens.
89+
90+
During the initial load, you will see the header and the footer collapsing together,
91+
and then, in a moment, a page content appearing between them. Exactly as on the video above.
92+
93+
Congratulations! We have just created a site with a layout shift.
94+
95+
<Note>
96+
To make the layout shift more noticeable, we added `header` and `footer` components to the layout
97+
</Note>
98+
99+
## Diagnosing a layout shift
100+
101+
Let's investigate the layout shift we just created. We are going to use **React Profiler** from the
102+
[React Developer Tools](https://react.dev/learn/react-developer-tools) to see what is happening.
103+
104+
Open **DevTools** in your browser and navigate to the **Profiler** tab. Record a profile of a page reload
105+
and take a closer look at the updates:
106+
107+
<Frame>
108+
<video
109+
muted
110+
loop
111+
playsInline
112+
controls
113+
title="React profiler"
114+
className="w-full aspect-video"
115+
src="/images/developer/guides/troubleshooting/layout-shift/react-profiler.mp4"
116+
></video>
117+
</Frame>
118+
119+
As you can see, the `SessionProvider` is updated twice. The first update corresponds to the
120+
initial render of the `SessionProvider` component:
121+
122+
<Frame>
123+
<img
124+
src="/images/developer/guides/troubleshooting/layout-shift/react-profiler-render-1.png"
125+
alt="First update to the SessionProvider"
126+
/>
127+
</Frame>
128+
129+
The second update corresponds to the re-render of the `SessionProvider` component and is caused by changed props:
130+
131+
<Frame>
132+
<img
133+
src="/images/developer/guides/troubleshooting/layout-shift/react-profiler-render-2.png"
134+
alt="Second update to the SessionProvider"
135+
/>
136+
</Frame>
137+
138+
## What's happening?
139+
140+
So what's going on here? Why is this happening?
141+
142+
This is related to the way React handles updates during the hydration process.
143+
144+
Let's take a closer look at the `SessionProvider` [component](https://next-auth.js.org/getting-started/client#sessionprovider).
145+
It accepts a `session` prop that is used to determine the current session state. The problem arises when the `session` prop is
146+
updated *during the hydration process*.
147+
148+
Here is the summary of what is going on:
149+
- The page is server-side rendered (SSR).
150+
- The server sends the HTML to the client, and React begins hydration.
151+
- While hydration is in progress, the client side receives a new update, in this case `session` value for the `SessionProvider` component.
152+
- This update interrupts hydration, causing React to discard the in-progress hydration and re-render the components on the client with the updated session value.
153+
154+
According to the React team's [comment](https://github.com/facebook/react/issues/24476#issuecomment-1127800350) in a GitHub issue, this is expected behavior and not considered a bug.
155+
156+
<Note>
157+
Diagnosing a layout shift caused by interrupted hydration can be tricky. React used to log a warning
158+
for this situation when using the Next.js Pages Router (though the App Router suppressed it). That warning
159+
[was removed](https://github.com/facebook/react/pull/25692) in later versions of React 18 and in React 19.
160+
</Note>
161+
162+
## Fixing a layout shift
163+
164+
Fixing a layout shift is not always straightforward. The issue we encountered in this guide could be fixed by providing `session` value to the `SessionProvider` component.
165+
This way, the session value will not change during the hydration process, and React will not need to re-render the rest of the components.
166+
167+
In more complex cases, you might need to use different approaches and techniques.
168+
169+
Here are some tips that might help to narrow down the issue:
170+
- Identify the components that are causing the layout shift.
171+
- Inspect all updates that occur during page loading and the hydration process.
172+
- For client components, provide the necessary props so server-rendered content matches the client-rendered content.
173+
174+
## References
175+
176+
We recommend checking out the following resources for more information on relevant topics:
177+
178+
- [Making Sense of React Server Components](https://www.joshwcomeau.com/react/server-components/)
179+
- [New Suspense SSR Architecture in React 18](https://github.com/reactwg/react-18/discussions/37)
180+
- [React Developer Tools](https://react.dev/learn/react-developer-tools)
181+
- [Cumulative Layout Shift (CLS)](https://web.dev/articles/cls)
182+
- [Debug layout shifts](https://web.dev/articles/debug-layout-shifts)
Binary file not shown.
497 KB
Loading
352 KB
Loading
Binary file not shown.

mint.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,10 @@
167167
},
168168
{
169169
"group": "Troubleshooting",
170-
"pages": ["developer/guides/troubleshooting/builder-is-not-loading"]
170+
"pages": [
171+
"developer/guides/troubleshooting/builder-is-not-loading",
172+
"developer/guides/troubleshooting/layout-shift"
173+
]
171174
},
172175
"developer/guides/environments",
173176
{

0 commit comments

Comments
 (0)