Vilva Athiban P B JavaScript developer. React, Node, GraphQL. Trying to make the web a better place to browse.

How to fix layout shifts to improve SEO in Next.js apps

4 min read 1277

Next.js Logo

Google tweaks its core search algorithm on an ongoing basis, and soon, overall web performance will play a bigger factor in page rankings. Google will evaluate the following three metrics collected from real users when determining rankings:

  • Largest Contentful Paint (LCP)
  • First Input Delay (FID)
  • Cumulative Layout Shift (CLS)

These metrics together comprise Google’s Web Vitals initiative. In this post, we will discuss CLS specifically, with special focus on how to prevent layout shifts in Next.js apps.

What is CLS?

Google defines Cumulative Layout Shift as follows:

CLS measures the sum total of all individual layout shift scores for every unexpected layout shift that occurs during the entire lifespan of the page. A layout shift occurs any time a visible element changes its position from one rendered frame to the next.

In the simplest terms, CLS is a measure of the shifts between components when a page loads. It’s ultimately represented by a layout shift score, calculated as below:

layout shift score = impact fraction * distance fraction

Where impact fraction is the sum of the area of impact of all the unstable elements between two frames, and distance fraction is the distance of an element’s or component’s movement between two frames.

Layout shifts are commonly caused by ads/embeds, dynamically injected content, and images without dimensions. Let’s explore how to handle CLS for each of these cases.

How to fix CLS

Ads/embeds

Ads are an important source of income for many webpages and can’t be ignored. When a new ad container is inserted into the DOM, however, it can cause a serious reflow or layout shift; it may move parts of the entire webpage. In addition to issues with insertion, if the ad container resizes due to its content or the ad library, this also creates additional friction.

We can fixed this by following the techniques below:

  • Build an empty container as a placeholder for the ad so that when an ad is inserted, it doesn’t move other elements on the page and reduces the CLS
  • Indicate maximum dimensions for the ad so it doesn’t resize and trigger layout shifts. If the ad is a banner or at the top of the viewport, pay even closer attention
  • In the event that no ad is returned, avoid removing the placeholder, as this will also trigger a layout shift

Dynamically injected content

Dynamic content is another major cause of layout shifts. Without placeholders, such content or components (e.g., an “accept cookies” container) can cause a serious layout shift. Even with placeholder containers, an increase or decrease in the size of the expected content will cause a layout shift.

Dynamic content can also be the result of a successful network call/API response. This data will initially be empty and load following a successful network call, causing layout shifts.

We made a custom demo for .
No really. Click here to check it out.

In addition to an optimized placeholder container, a skeleton UI is an excellent fix for dynamic content. Consider an ecommerce product page as an example. A skeleton component of each product item will create a clean layout, and after a successful API response, these skeleton components are updated without major layout shifts.

Images without dimensions

Images without dimensions are one of the most common yet easily fixable causes of CLS. An image without predefined dimensions won’t take up any space on the screen until it loads. Thus, when it does load, it shifts all the elements around it, thereby causing a domino effect of layout shifts.

We can fix this by adding height and width attributes to the image tags:

<img src="example.jpg" width="640" height="360" alt="Alt text">

This is a fixed height and width, however, which would still cause a layout shift in a responsive design. Hence, in responsive design, fixed dimensions aren’t much help. Also, since CSS has become a much more desirable way to size images, we need a better solution.

This is where the CSS aspect-ratio property comes into picture. We still pass on height and width attributes but use aspect-ratio to maintain the ratio between those attributes.

<!-- set a 640:360 i.e a 16:9 - aspect ratio -->
<img src="example.jpg" width="640" height="360" alt="Alt text">

<!-- CSS -->
<style>
img {
    aspect-ratio: attr(width) / attr(height);
}
</style>

Here we maintain the aspect ratio as a constant value irrespective of the actual image size, which helps us to better fight CLS. Unfortunately, all too often, developers either ignore or deprioritize this relatively simple optimization.

Fixing CLS in Next.js

Next.js is a progressive framework built on React that aims to implement defaults and constraints to fix the issues we discussed above. Let’s see how to leverage the above techniques with Next.js and how they’re supported out of the box.

Images and Next.js

As discussed above, images without dimensions are a major cause of layout shifts. To fight this, Next.js has an Image component, which is a wrapper over the img element.

What does this component do, exactly?

  • height and width properties are both optional in the img tag. As a result, many developers tend to ignore it, causing a bad CLS score. The Next.js Image component makes height and width required props, keeping the developer honest. It also maintains aspect-ratio out of the box
  • The Image component has magical support for srcsets, thereby enabling a smooth experience across devices and viewports of different sizes
  • It also supports preloading images when required. However, by default images are lazy loaded to improve performance

Here’s an example of the syntax:

<Image
  src="/example.png"
  alt="Alt text"
  width={500}
  height={500}
/>

Ads and Next.js

Though Next.js doesn’t have out-of-the-box support for ads, we can leverage the Layout component to better handle layout shifts. Next.js lets us create a Layout component that can be used as a basic layout/structure for all the pages in the app.

Ads are generally placed in the same location across all pages for a given web app. We can create a Layout component with placeholder containers for ads and include it in the _app.js file. This enables ad containers on every page, and when the ad loads, there should be minimal to no layout shifts.

We can also leverage this for a skeleton UI depending, on the type of web application. See the syntax below:

# Layout.jsx
export const Layout = () => (
  <div>
    <AdContainer />
    <Header />
    <LeftMenu />
  </div>
)

# _app.js
import { Layout } from '../components/Layout'
export default function MyApp({ Component, pageProps }) {
  return (
    <Layout>
      <Component {...pageProps} />
    </Layout>
  )
}

In this way, we can handle layout shifts for the whole application in single point.

Bonus tip: CLS and styled-components with Next.js

If you are using styled-components with Next.js and didn’t pay close enough attention, there will be huge layout shifts. This is because Next.js is SSR by default, and therefore, it loads JavaScript to the client with base styles. styled-components, on the other hand, is executed at runtime in the browser.

This forces new styles onto the webpage, causing repaint and reflow. To avoid this, use babel-plugin-styled-components and make use of the styled-components ServerStyleSheet in the _document.js to apply styles on the server side, avoiding reflows after rendering on the browser.

Conclusion

By following the above techniques, we can drastically reduce layout shifts in Next.js apps. This is necessary to maintain good SEO, but more than that, it creates a smoother and more pleasing user experience on our web apps.

LogRocket: Full visibility into production Next.js apps

Debugging Next applications can be difficult, especially when users experience issues that are difficult to reproduce. If you’re interested in monitoring and tracking state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket is like a DVR for web apps, recording literally everything that happens on your Next app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your Next.js apps — .

Vilva Athiban P B JavaScript developer. React, Node, GraphQL. Trying to make the web a better place to browse.

Leave a Reply