You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
description: Learn how to identify and diagnose layout shifts on initial page load in your app
3
+
description: Learn how to fix a layout shift on initial page load in your app
4
4
---
5
5
6
-
## What is a layout shift?
6
+
{/* @todo(sasha): Update with a correct link to the blog post once it is ready */}
7
+
{/* <Note>
8
+
For a deeper dive into diagnosing layout shifts, check out our blog post: [Diagnosing a Layout Shift](/blog/diagnosing-layout-shifts)
9
+
</Note> */}
7
10
8
-
Layout shift is a common issue in web development where elements on a page move unexpectedly,
9
-
causing a poor user experience.
11
+
<h2>Rerender During Hydration</h2>
10
12
11
-
Here is an example of a layout shift:
13
+
Watch the example below to recognize a layout shift caused by a component update during hydration. If your app behaves similarly, follow the steps in this guide.
12
14
13
15
<Frame>
14
16
<video
@@ -22,168 +24,70 @@ Here is an example of a layout shift:
22
24
></video>
23
25
</Frame>
24
26
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
-
## Layout shift due to context update during hydration
31
-
32
-
Let's illustrate how layout shift can occur in a real-world scenario. In this example, we will
33
-
investigate a layout shift caused by a context update during the hydration process.
34
-
35
-
### Reproducing the issue
36
-
37
-
Imagine you have a Makeswift site and you want to use `next-auth` for authentication capabilities.
38
-
So you add `SessionProvider` to manage the session state.
39
-
40
-
Since `SessionProvider` is a client component, you'd probably create a wrapper component, say `Providers`,
41
-
to wrap your `SessionProvider` and use it in your `layout.tsx` file.
42
-
43
-
Here is the code you might end up with.
44
-
45
-
<CodeGroup>
46
-
```tsx src/app/providers.tsx
47
-
'use client'
48
-
49
-
import { SessionProvider } from'next-auth/react'
50
-
51
-
exportfunction Providers({ children }: { children:React.ReactNode }) {
Nothing seems wrong with this code, right? Now let's run the app and see what happens.
92
-
93
-
During the initial load, you will see the header and the footer collapsing together,
94
-
and then, in a moment, a page content appearing between them. Exactly as on the video above.
95
-
96
-
Congratulations! We have just created a site with a layout shift.
27
+
## Background
97
28
98
-
<Note>
99
-
To make the layout shift more noticeable, we added `header` and `footer` components to the layout
100
-
</Note>
29
+
Such shifts happen when a component's props, context, or state change mid-hydration, triggering a Suspense fallback and a visual jump.
101
30
102
-
### Diagnosing a layout shift
31
+
One potential cause is a context provider whose value changes between server-side rendering (SSR) and client-side rendering (CSR). This can happen if the provider's value is derived from a state or prop that is initialized differently on the server and client.
103
32
104
-
Let's investigate the layout shift we just created. We are going to use **React Profiler** from the
105
-
[React Developer Tools](https://react.dev/learn/react-developer-tools) to see what is happening.
33
+
Here is the summary of how this can happen:
34
+
- The page is server-side rendered (SSR).
35
+
- The server sends the HTML to the client, and React begins hydration.
36
+
- While hydration is in progress, the client side receives a new update.
37
+
- 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.
38
+
- While client side re-rendering is happening, React replaces the server rendered content with a client side fallback found in a closest `Suspense` boundary. The fallback happens to be `null` in this case. This effectively causes the layout shift.
106
39
107
-
Open **DevTools** in your browser and navigate to the **Profiler** tab. Record a profile of a page reload
108
-
and take a closer look at the updates:
40
+
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.
Makeswift runtime library wraps all page components in a `Suspense` boundary when serving a page.
44
+
</Info>
120
45
121
-
As you can see, the `SessionProvider` is updated twice. The first update corresponds to the
122
-
initial render of the `SessionProvider` component:
46
+
<Note>
47
+
React used to log a warning for this situation when using the Next.js Pages Router (though the App Router suppressed it). That warning [was removed](https://github.com/facebook/react/pull/25692) in later versions of React 18 and in React 19.
- Load your page fresh (SSR) and look for sudden collapses or reflows. E.g., headers, nav menus, or sections disappearing briefly—then snapping into place.
55
+
- In Chrome DevTools → **Performance**, you'll see “layout-shift” entries aligned with two render passes.
139
56
140
-
### What's happening?
57
+
### 2. Record with React DevTools Profiler
141
58
142
-
So what's going on here? Why is this happening?
59
+
- Open the **Profiler**, start recording, then reload the page.
60
+
- Stop recording after the shift. In the flame chart, find two consecutive renders of the same component (hydration then update).
143
61
144
-
This is related to the way React handles updates during the hydration process.
62
+
### 3. Pinpoint the Problematic Update
145
63
146
-
Let's take a closer look at the `SessionProvider`[component](https://next-auth.js.org/getting-started/client#sessionprovider).
147
-
It accepts a `session` prop that is used to determine the current session state. The problem arises when the `session` prop is
148
-
updated *during the hydration process*.
64
+
- Examine which props, context values, or state differ between those renders.
65
+
- Common culprits:
66
+
- Context providers whose value identity changes
67
+
- State initialized differently on client vs. server
68
+
- Data fetched in a `useEffect` (runs post-hydration)
149
69
150
-
Here is the summary of what is going on:
151
-
- The page is server-side rendered (SSR).
152
-
- The server sends the HTML to the client, and React begins hydration.
153
-
- While hydration is in progress, the client side receives a new update, in this case `session` value for the `SessionProvider` component.
154
-
- 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.
155
-
- While client side re-rendering is happening, React replaces the server rendered content with a client side fallback found in a closest `Suspense` boundary. The fallback happens to be `null` in this case. This effectively causes the layout shift.
70
+
### 4. Isolate the Component
156
71
157
-
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.
72
+
- Temporarily comment out or disable the suspected update (e.g., remove the provider or delay the state change).
73
+
- Confirm that the shift disappears when that update is skipped.
158
74
159
-
<Info>
160
-
Makeswift runtime library wraps all page components in a `Suspense` boundary when serving a page.
161
-
</Info>
75
+
### 5. Apply a Permanent Fix
162
76
163
-
<Note>
164
-
Diagnosing a layout shift caused by interrupted hydration can be tricky. React used to log a warning
165
-
for this situation when using the Next.js Pages Router (though the App Router suppressed it). That warning
166
-
[was removed](https://github.com/facebook/react/pull/25692) in later versions of React 18 and in React 19.
167
-
</Note>
77
+
-**Match SSR & Client:** Ensure initial props/state are identical. For example, compute or fetch values during SSR so the client sees the same markup.
78
+
-**Memoize Values:** Wrap dynamic context or prop values in [`useMemo`](https://react.dev/reference/react/useMemo) to avoid identity changes during hydration.
79
+
-**Defer Non-Critical Updates:** Use React's [`startTransition`](https://react.dev/reference/react/startTransition) to mark updates as non-urgent, preventing fallback rendering.
168
80
169
-
### Fixing a layout shift
81
+
### 6. Verify the Resolution
170
82
171
-
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.
172
-
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.
83
+
- Re-run your Profiler trace: you should now see only one render with no fallback UI invoked during hydration.
84
+
- Confirm that no “layout-shift” entries align with any component updates.
173
85
174
-
In more complex cases, you might need to use different approaches and techniques.
86
+
---
175
87
176
-
Here are some tips that might help to narrow down the issue:
177
-
- Identify the components that are causing the layout shift.
178
-
- Inspect all updates that occur during page loading and the hydration process.
179
-
- For client components, provide the necessary props so server-rendered content matches the client-rendered content.
88
+
By following this reproduce → profile → isolate → fix workflow, you'll eliminate jumps caused by component updates during React hydration.
180
89
181
90
## References
182
91
183
-
We recommend checking out the following resources for more information on relevant topics:
184
-
185
-
-[Making Sense of React Server Components](https://www.joshwcomeau.com/react/server-components/)
186
92
-[New Suspense SSR Architecture in React 18](https://github.com/reactwg/react-18/discussions/37)
0 commit comments