Sites Now Become Interactive 50% Faster

This article is part of a series that explores the technologies that keep your Framer site fast and operational.

This week, we’ve started rolling out an update that makes most Framer sites become interactive 50% faster. Here’s how we achieved this and what interactivity means.

How do Framer sites become interactive?

A website is made of HTML and made interactive with JavaScript. To ensure the initial page load is fast, we send HTML first, so users (and search engines) see content as fast as possible. This is usually called server-side rendering.

In parallel, we load the JavaScript that is needed for a process called “hydration” (yes, like watering plants 🫗🪴). The process itself is handled by our framework of choice, React, which powers every Framer site. Technically, hydration takes the received HTML and attaches event listeners, e.g., it makes elements listen for ‘clicks’ or ‘scroll’. This is when your website is able to process user interactions and when we call it ‘interactive’.

Some of the JavaScript-based elements need data that has to be fetched from the server to provide interactivity. For example, our homepage needs the user login state in the areas marked in red:

On other Framer sites it could be fetching reviews, the latest blog posts or even just the weather. Every fetch takes a little bit of time. Especially if a fetch leads to another fetch, which is pretty common. This is all done during hydration.

From here, we’ll go more technical to show you what’s actually happening under the hood. If you’re less interested in the technical details, you can skip right to the results in the last chapter.

React: How Suspense & Hydration play together

For data fetching during hydration, we use a React feature called ‘Suspense’, which is a way of telling React ‘This component is waiting for something’ — in our case, waiting for data received from network requests.

Previously, we’ve used a single instance of Suspense for that purpose. Here’s very simplified how that looked like:

<App>
  <Suspense>
    <Page>
      <Header />
      <DataFetching content="blog" /> {/* ⬅️ triggers Suspense */}
      <DataFetching content="footer" /> {/* ⬅️ triggers Suspense */}
    </Page>
  </Suspense>
</App>

This means, whenever we fetch data for ‘blog’ and ‘footer’, the single Suspense tag (“Suspense boundary”) would trigger (also called ‘suspending’). While React initializes the data fetching of those two components in parallel, the devil is in the details:

During hydration, whenever one component suspends and then after successfully fetching data, unsuspends, hydration restarts from the Suspense boundary that caught the suspending component.

This means in the example above, for every single data fetch, React would start hydration from the single Suspense tag below <App>. More specifically, when fetching data for ‘blog’, React triggers the render process for <Header> and the children of both <DataFetching> components all over again. This also happens when fetching data for ‘footer’.

Basically, we’d start all over lots of times. Each fetching component would cause a parent to render again. In the example, if we assume each data fetcher has children, we'd get a total of 12 renders, as <Page> and <Header> would render 3x, the blog data fetcher & children 2x, and the footer data fetcher & children 1x (= 6 + 4 + 2).

Faster Hydration with granular Suspense

Rendering just a handful of components like in the example is fast, but real websites fetch lots of data and contain hundreds or thousands of components. Also, how fast rendering takes depends on the device (especially on the CPU). Some users might have a slower device than others, and we also want them to enjoy the same speedy experience.

As a first instinct, we reached for memoization (React.memo) — surely that would stop React from rendering those components again, right? No, because React schedules re-renders for the children of the Suspense boundary.

To fix it, we’ve added granular Suspense boundaries around components that fetch data:

<App>
  <Suspense>
    <Page>
      <Header />
      <Suspense><DataFetching content="blog" /></Suspense> {/* 🎉 has its own boundary */}
      <Suspense><DataFetching content="footer" /></Suspense> {/* 🎉 has its own boundary */}
    </Page>
  </Suspense>
</App>

Now each Suspense boundary catches suspending components in the tree-below them. This means, React resumes hydration right from where it paused. As an example, the Framer homepage went from 1 to 151 granular boundaries.

This makes it a fast, linear process O(n) (with n being the number of nodes in the component tree) instead, as every component renders just once during hydration, no matter how many data fetchers are in the tree.

What this means for Framer sites

As measured on sites that joined our beta test, the change makes hydration 50-80% faster on many devices. Depending on how much data a site needs during hydration, it even becomes up to 200% faster on slower devices.

(left: before, right: after. Recorded on a M2 Pro with 6x CPU throttling & 4G fast in a fresh Chrome profile.)

All in all, this means scroll animations start working earlier and users can interact with Framer sites sooner. While at it, we’ve also made improvements to pages with many SVGs, where we now start hydration in parallel to SVG loading. This makes the cookie banner show up faster — up to 42% earlier than before, measured across all Framer sites visited by mobile devices.

Even fast devices benefit from those changes. This is on an unthrottled M2 Pro Mac laptop with a 100 MBit/s connection:

(left: before, right: after. Recorded on a M2 Pro Mac with a 100 MBit/s internet connection in a fresh Chrome profile.)

If you're a Framer user, this change will be automatically applied while editing and re-publishing your site.


Special thanks to our Performance Engineer Ivan Akulov for investigating and finding the cause initially.

——

If you have any performance related questions or concerns, our performance team is happy to talk to you in our community forum.

How do Framer sites become interactive?

A website is made of HTML and made interactive with JavaScript. To ensure the initial page load is fast, we send HTML first, so users (and search engines) see content as fast as possible. This is usually called server-side rendering.

In parallel, we load the JavaScript that is needed for a process called “hydration” (yes, like watering plants 🫗🪴). The process itself is handled by our framework of choice, React, which powers every Framer site. Technically, hydration takes the received HTML and attaches event listeners, e.g., it makes elements listen for ‘clicks’ or ‘scroll’. This is when your website is able to process user interactions and when we call it ‘interactive’.

Some of the JavaScript-based elements need data that has to be fetched from the server to provide interactivity. For example, our homepage needs the user login state in the areas marked in red:

On other Framer sites it could be fetching reviews, the latest blog posts or even just the weather. Every fetch takes a little bit of time. Especially if a fetch leads to another fetch, which is pretty common. This is all done during hydration.

From here, we’ll go more technical to show you what’s actually happening under the hood. If you’re less interested in the technical details, you can skip right to the results in the last chapter.

React: How Suspense & Hydration play together

For data fetching during hydration, we use a React feature called ‘Suspense’, which is a way of telling React ‘This component is waiting for something’ — in our case, waiting for data received from network requests.

Previously, we’ve used a single instance of Suspense for that purpose. Here’s very simplified how that looked like:

<App>
  <Suspense>
    <Page>
      <Header />
      <DataFetching content="blog" /> {/* ⬅️ triggers Suspense */}
      <DataFetching content="footer" /> {/* ⬅️ triggers Suspense */}
    </Page>
  </Suspense>
</App>

This means, whenever we fetch data for ‘blog’ and ‘footer’, the single Suspense tag (“Suspense boundary”) would trigger (also called ‘suspending’). While React initializes the data fetching of those two components in parallel, the devil is in the details:

During hydration, whenever one component suspends and then after successfully fetching data, unsuspends, hydration restarts from the Suspense boundary that caught the suspending component.

This means in the example above, for every single data fetch, React would start hydration from the single Suspense tag below <App>. More specifically, when fetching data for ‘blog’, React triggers the render process for <Header> and the children of both <DataFetching> components all over again. This also happens when fetching data for ‘footer’.

Basically, we’d start all over lots of times. Each fetching component would cause a parent to render again. In the example, if we assume each data fetcher has children, we'd get a total of 12 renders, as <Page> and <Header> would render 3x, the blog data fetcher & children 2x, and the footer data fetcher & children 1x (= 6 + 4 + 2).

Faster Hydration with granular Suspense

Rendering just a handful of components like in the example is fast, but real websites fetch lots of data and contain hundreds or thousands of components. Also, how fast rendering takes depends on the device (especially on the CPU). Some users might have a slower device than others, and we also want them to enjoy the same speedy experience.

As a first instinct, we reached for memoization (React.memo) — surely that would stop React from rendering those components again, right? No, because React schedules re-renders for the children of the Suspense boundary.

To fix it, we’ve added granular Suspense boundaries around components that fetch data:

<App>
  <Suspense>
    <Page>
      <Header />
      <Suspense><DataFetching content="blog" /></Suspense> {/* 🎉 has its own boundary */}
      <Suspense><DataFetching content="footer" /></Suspense> {/* 🎉 has its own boundary */}
    </Page>
  </Suspense>
</App>

Now each Suspense boundary catches suspending components in the tree-below them. This means, React resumes hydration right from where it paused. As an example, the Framer homepage went from 1 to 151 granular boundaries.

This makes it a fast, linear process O(n) (with n being the number of nodes in the component tree) instead, as every component renders just once during hydration, no matter how many data fetchers are in the tree.

What this means for Framer sites

As measured on sites that joined our beta test, the change makes hydration 50-80% faster on many devices. Depending on how much data a site needs during hydration, it even becomes up to 200% faster on slower devices.

(left: before, right: after. Recorded on a M2 Pro with 6x CPU throttling & 4G fast in a fresh Chrome profile.)

All in all, this means scroll animations start working earlier and users can interact with Framer sites sooner. While at it, we’ve also made improvements to pages with many SVGs, where we now start hydration in parallel to SVG loading. This makes the cookie banner show up faster — up to 42% earlier than before, measured across all Framer sites visited by mobile devices.

Even fast devices benefit from those changes. This is on an unthrottled M2 Pro Mac laptop with a 100 MBit/s connection:

(left: before, right: after. Recorded on a M2 Pro Mac with a 100 MBit/s internet connection in a fresh Chrome profile.)

If you're a Framer user, this change will be automatically applied while editing and re-publishing your site.


Special thanks to our Performance Engineer Ivan Akulov for investigating and finding the cause initially.

——

If you have any performance related questions or concerns, our performance team is happy to talk to you in our community forum.

How do Framer sites become interactive?

A website is made of HTML and made interactive with JavaScript. To ensure the initial page load is fast, we send HTML first, so users (and search engines) see content as fast as possible. This is usually called server-side rendering.

In parallel, we load the JavaScript that is needed for a process called “hydration” (yes, like watering plants 🫗🪴). The process itself is handled by our framework of choice, React, which powers every Framer site. Technically, hydration takes the received HTML and attaches event listeners, e.g., it makes elements listen for ‘clicks’ or ‘scroll’. This is when your website is able to process user interactions and when we call it ‘interactive’.

Some of the JavaScript-based elements need data that has to be fetched from the server to provide interactivity. For example, our homepage needs the user login state in the areas marked in red:

On other Framer sites it could be fetching reviews, the latest blog posts or even just the weather. Every fetch takes a little bit of time. Especially if a fetch leads to another fetch, which is pretty common. This is all done during hydration.

From here, we’ll go more technical to show you what’s actually happening under the hood. If you’re less interested in the technical details, you can skip right to the results in the last chapter.

React: How Suspense & Hydration play together

For data fetching during hydration, we use a React feature called ‘Suspense’, which is a way of telling React ‘This component is waiting for something’ — in our case, waiting for data received from network requests.

Previously, we’ve used a single instance of Suspense for that purpose. Here’s very simplified how that looked like:

<App>
  <Suspense>
    <Page>
      <Header />
      <DataFetching content="blog" /> {/* ⬅️ triggers Suspense */}
      <DataFetching content="footer" /> {/* ⬅️ triggers Suspense */}
    </Page>
  </Suspense>
</App>

This means, whenever we fetch data for ‘blog’ and ‘footer’, the single Suspense tag (“Suspense boundary”) would trigger (also called ‘suspending’). While React initializes the data fetching of those two components in parallel, the devil is in the details:

During hydration, whenever one component suspends and then after successfully fetching data, unsuspends, hydration restarts from the Suspense boundary that caught the suspending component.

This means in the example above, for every single data fetch, React would start hydration from the single Suspense tag below <App>. More specifically, when fetching data for ‘blog’, React triggers the render process for <Header> and the children of both <DataFetching> components all over again. This also happens when fetching data for ‘footer’.

Basically, we’d start all over lots of times. Each fetching component would cause a parent to render again. In the example, if we assume each data fetcher has children, we'd get a total of 12 renders, as <Page> and <Header> would render 3x, the blog data fetcher & children 2x, and the footer data fetcher & children 1x (= 6 + 4 + 2).

Faster Hydration with granular Suspense

Rendering just a handful of components like in the example is fast, but real websites fetch lots of data and contain hundreds or thousands of components. Also, how fast rendering takes depends on the device (especially on the CPU). Some users might have a slower device than others, and we also want them to enjoy the same speedy experience.

As a first instinct, we reached for memoization (React.memo) — surely that would stop React from rendering those components again, right? No, because React schedules re-renders for the children of the Suspense boundary.

To fix it, we’ve added granular Suspense boundaries around components that fetch data:

<App>
  <Suspense>
    <Page>
      <Header />
      <Suspense><DataFetching content="blog" /></Suspense> {/* 🎉 has its own boundary */}
      <Suspense><DataFetching content="footer" /></Suspense> {/* 🎉 has its own boundary */}
    </Page>
  </Suspense>
</App>

Now each Suspense boundary catches suspending components in the tree-below them. This means, React resumes hydration right from where it paused. As an example, the Framer homepage went from 1 to 151 granular boundaries.

This makes it a fast, linear process O(n) (with n being the number of nodes in the component tree) instead, as every component renders just once during hydration, no matter how many data fetchers are in the tree.

What this means for Framer sites

As measured on sites that joined our beta test, the change makes hydration 50-80% faster on many devices. Depending on how much data a site needs during hydration, it even becomes up to 200% faster on slower devices.

(left: before, right: after. Recorded on a M2 Pro with 6x CPU throttling & 4G fast in a fresh Chrome profile.)

All in all, this means scroll animations start working earlier and users can interact with Framer sites sooner. While at it, we’ve also made improvements to pages with many SVGs, where we now start hydration in parallel to SVG loading. This makes the cookie banner show up faster — up to 42% earlier than before, measured across all Framer sites visited by mobile devices.

Even fast devices benefit from those changes. This is on an unthrottled M2 Pro Mac laptop with a 100 MBit/s connection:

(left: before, right: after. Recorded on a M2 Pro Mac with a 100 MBit/s internet connection in a fresh Chrome profile.)

If you're a Framer user, this change will be automatically applied while editing and re-publishing your site.


Special thanks to our Performance Engineer Ivan Akulov for investigating and finding the cause initially.

——

If you have any performance related questions or concerns, our performance team is happy to talk to you in our community forum.

TLDR

TLDR

  • Interactive websites use JavaScript to respond to user actions, like clicking buttons or typing text. Framer sites become interactive through a process called hydration, done by React, our framework of choice.

  • Data fetching is the process of getting information from the internet. Some elements need to fetch data before they can become interactive. For that, we use a React feature called Suspense. Previously, a single Suspense boundary was used, causing hydration to restart from the beginning for each data fetch.

  • We've made it 50%+ faster by optimizing how we use Suspense. Now, when a site fetches data, only the part that needs it re-renders, which speeds up this process significantly.

  • Interactive websites use JavaScript to respond to user actions, like clicking buttons or typing text. Framer sites become interactive through a process called hydration, done by React, our framework of choice.

  • Data fetching is the process of getting information from the internet. Some elements need to fetch data before they can become interactive. For that, we use a React feature called Suspense. Previously, a single Suspense boundary was used, causing hydration to restart from the beginning for each data fetch.

  • We've made it 50%+ faster by optimizing how we use Suspense. Now, when a site fetches data, only the part that needs it re-renders, which speeds up this process significantly.

  • Interactive websites use JavaScript to respond to user actions, like clicking buttons or typing text. Framer sites become interactive through a process called hydration, done by React, our framework of choice.

  • Data fetching is the process of getting information from the internet. Some elements need to fetch data before they can become interactive. For that, we use a React feature called Suspense. Previously, a single Suspense boundary was used, causing hydration to restart from the beginning for each data fetch.

  • We've made it 50%+ faster by optimizing how we use Suspense. Now, when a site fetches data, only the part that needs it re-renders, which speeds up this process significantly.

Beyond the Canvas

Beyond the Canvas

Beyond the Canvas