| 
 | 1 | +---  | 
 | 2 | +title: Layout shift on initial page load  | 
 | 3 | +description: Learn how to identify and diagnose layout shifts on initial page load 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 | +## 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 | +export function Providers({ children }: { children: React.ReactNode }) {  | 
 | 52 | +  return <SessionProvider>{children}</SessionProvider>  | 
 | 53 | +}  | 
 | 54 | +```  | 
 | 55 | + | 
 | 56 | +```tsx src/app/layout.tsx  | 
 | 57 | +// ...  | 
 | 58 | +import { Providers } from './providers'  | 
 | 59 | + | 
 | 60 | +export default async function RootLayout({  | 
 | 61 | +  children,  | 
 | 62 | +}: Readonly<{  | 
 | 63 | +  children: React.ReactNode  | 
 | 64 | +}>) {  | 
 | 65 | +  return (  | 
 | 66 | +    <html lang="en">  | 
 | 67 | +      <head>  | 
 | 68 | +        <DraftModeScript />  | 
 | 69 | +      </head>  | 
 | 70 | +      <body className={clsx(inter.className, 'bg-gray-600')}>  | 
 | 71 | +        <MakeswiftProvider previewMode={(await draftMode()).isEnabled}>  | 
 | 72 | +          <Providers>  | 
 | 73 | +            <header className="flex items-center justify-between bg-gray-600 p-4 px-10 text-white">  | 
 | 74 | +              <h1 className="text-3xl font-bold underline">header</h1>  | 
 | 75 | +            </header>  | 
 | 76 | + | 
 | 77 | +            {children}  | 
 | 78 | +              | 
 | 79 | +            <footer className="flex items-center justify-between bg-gray-600 p-4 px-10 text-white">  | 
 | 80 | +              <h1 className="text-3xl font-bold underline">footer</h1>  | 
 | 81 | +            </footer>  | 
 | 82 | +          </Providers>  | 
 | 83 | +        </MakeswiftProvider>  | 
 | 84 | +      </body>  | 
 | 85 | +    </html>  | 
 | 86 | +  )  | 
 | 87 | +}  | 
 | 88 | +```  | 
 | 89 | +</CodeGroup>  | 
 | 90 | + | 
 | 91 | +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.  | 
 | 97 | + | 
 | 98 | +<Note>  | 
 | 99 | +To make the layout shift more noticeable, we added `header` and `footer` components to the layout  | 
 | 100 | +</Note>  | 
 | 101 | + | 
 | 102 | +### Diagnosing a layout shift  | 
 | 103 | + | 
 | 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.  | 
 | 106 | + | 
 | 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:  | 
 | 109 | + | 
 | 110 | +<Frame>  | 
 | 111 | +  <video  | 
 | 112 | +    muted  | 
 | 113 | +    playsInline  | 
 | 114 | +    controls  | 
 | 115 | +    title="React profiler"  | 
 | 116 | +    className="w-full aspect-video"  | 
 | 117 | +    src="/images/developer/guides/troubleshooting/layout-shift/react-profiler.mp4"  | 
 | 118 | +  ></video>  | 
 | 119 | +</Frame>  | 
 | 120 | + | 
 | 121 | +As you can see, the `SessionProvider` is updated twice. The first update corresponds to the  | 
 | 122 | +initial render of the `SessionProvider` component:  | 
 | 123 | + | 
 | 124 | +<Frame>  | 
 | 125 | +  <img  | 
 | 126 | +    src="/images/developer/guides/troubleshooting/layout-shift/react-profiler-render-1.png"  | 
 | 127 | +    alt="First update to the SessionProvider"  | 
 | 128 | +  />  | 
 | 129 | +</Frame>  | 
 | 130 | + | 
 | 131 | +The second update corresponds to the re-render of the `SessionProvider` component and is caused by changed props:  | 
 | 132 | + | 
 | 133 | +<Frame>  | 
 | 134 | +  <img  | 
 | 135 | +    src="/images/developer/guides/troubleshooting/layout-shift/react-profiler-render-2.png"  | 
 | 136 | +    alt="Second update to the SessionProvider"  | 
 | 137 | +  />  | 
 | 138 | +</Frame>  | 
 | 139 | + | 
 | 140 | +### What's happening?  | 
 | 141 | + | 
 | 142 | +So what's going on here? Why is this happening?  | 
 | 143 | + | 
 | 144 | +This is related to the way React handles updates during the hydration process.  | 
 | 145 | + | 
 | 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*.  | 
 | 149 | + | 
 | 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.  | 
 | 156 | + | 
 | 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.  | 
 | 158 | + | 
 | 159 | +<Info>  | 
 | 160 | +Makeswift runtime library wraps all page components in a `Suspense` boundary when serving a page.  | 
 | 161 | +</Info>  | 
 | 162 | + | 
 | 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>  | 
 | 168 | + | 
 | 169 | +### Fixing a layout shift  | 
 | 170 | + | 
 | 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.  | 
 | 173 | + | 
 | 174 | +In more complex cases, you might need to use different approaches and techniques.  | 
 | 175 | + | 
 | 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.  | 
 | 180 | + | 
 | 181 | +## References  | 
 | 182 | + | 
 | 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 | +- [New Suspense SSR Architecture in React 18](https://github.com/reactwg/react-18/discussions/37)  | 
 | 187 | +- [React Developer Tools](https://react.dev/learn/react-developer-tools)  | 
 | 188 | +- [Cumulative Layout Shift (CLS)](https://web.dev/articles/cls)  | 
 | 189 | +- [Debug layout shifts](https://web.dev/articles/debug-layout-shifts)  | 
0 commit comments