CMS Performance Big Wins for Attributes System

This article covers performance enhancements that are included when using the Webflow CMS solutions in Attributes.


IntroPerformance big wins Setup basics CDN overview Loading paginated items Benchmark test Rendering paginated items DOM size Don't block the main thread Assets download Lighthouse core vitals Filtering and sorting itemsConclusion
This is some text inside of a div block.


Most of the things that make Finsweet so special happen behind the screen. Whether it's designing a beautiful Webflow site or developing a product that simplifies your Webflow workflow, most of the complexity is hidden behind a simple UI.

This is the case for most good software and almost everything in tech. This is because most people do not care about how a product works, they just care that it does the job they need it to do.

But you are not most people are you? You are a visual software developer who dabbles in low code and you want to know more about why things work and how to improve performance at every step. We know this because we know you. And we know you because we are you.

Here at Finsweet, we care deeply about the solutions and products we build for the Webflow community. We add so many awesome optimizations throughout our tools that never get noticed because they just work.

That being said, many of you have been asking us to go deeper and explain how the tools we build actually work. That is the purpose of these comparison articles. Our goal is to share our process for optimizing our Webflow toolkit — and to hopefully teach you something new along the way.

This article is packed with juicy technical content that can help you get to the next level of technical understanding in Webflow.

Performance Comparison

Fair Tests

Throughout this article we share speed tests we conducted for Attributes. These tests were made in a Webflow project with a 2,000 item CMS Collection list and a complex filtering system.

Setup Basics

For each Attribute solution, a solution-specific script is added to the page. Below is an example of three Attributes solutions added to a page — Filter, Load, and Sort.

<!-- Functionality for CMS Filter -->
<script async src="[email protected]/cmsfilter.js"></script>

<!-- Functionality for CMS Load -->
<script async src="[email protected]/cmsload.js"></script>

<!-- Functionality for CMS Sort -->
<script async src="[email protected]/cmssort.js"></script>

Each Attributes solution is split into its own package (separate scripts) to keep the setup modular. This means Attributes only loads the minimum required JavaScript to make the desired functionality work.

When using CMS Attributes, an additional <span class="article_code">cmscore</span> script is dynamically downloaded in the background, which contains a set of global functionalities that are used across all CMS packages.

Instead of adding those global functionalities in every CMS script, Attributes uses a single core package that can be accessed by all CMS solutions. This avoids downloading the same global functionality code when using multiple of them. The CMS core package enhancement is our <span class="article_code">cmscore.js</span> file.

Each attribute functionality is contained into its own package that is only loaded when required.
For example, if you wanted to only use CMS Filter, you would only need to add the CMS Filter script to the page. CMS Load and CMS Sort do not need to be added to the page since they are not being used. We will only load the JavaScript for the required functionality.

Each Attributes solution is loaded asynchronously in the background and remains completely independent of any other attribute. This means that no functionality will be paused or interrupted while waiting for another to load.

For example, if a project uses Attributes <span class="article_code">cmsfilter</span>, <span class="article_code">cmssort</span> and <span class="article_code">cmsload</span> :

  • Whenever the first package is loaded (<span class="article_code">cmsfilter</span> for example), the CMS Filter functionality is immediately available and the user can start filtering the list.
  • Even if the other packages take longer to load (or even if they don’t load at all), the already loaded packages will always work correctly. Users do not need to wait for all packages to load to start using CMS Filter.
  • As soon as any other package is loaded (<span class="article_code">cmsload</span> for example), it automatically couples with the previously loaded packages and starts running its task.
  • Everything happens seamlessly in the background regardless of the load time for each package, without affecting UX.
  • ~On average, each Attribute takes an average of <span class="article_code">30ms</span> to load and all packages are loaded simultaneously.

CDN overview

Attributes is served through a provider called JsDelivr. They host files across four CDN providers with fallbacks to cover possible outages. Whenever a JsDelivr CDN provider stops working correctly, the network will automatically fall back to the next and closest CDN to assure there are no outages. These redundancies help keep Attributes live even when a major outage occurs.

network CDN providers map
JsDelivr’s network is composed of 4 different CDN providers

Loading paginated items

Attributes has the ability to load the exact amount of pages needed for each Collection List by referencing Webflow’s native pagination features. By enabling the native Webflow <span class="article_code">Page Count</span> element in the Collection List’s settings, Attributes is able to detect how many pages the list has in total. This allows Attributes to optimize the loading process by fetching all of the pages at once rather than in batches.

Look at the network requests waterfall:

load attributes waterfall
Network requests waterfall shows Attributes loading 19 CMS pages at once. The entire process takes an average of just 130ms.

This approach is beneficial because:

  • It loads only the required pages — not less, not more.
  • It asynchronously loads all pages in a single batch, not in multiple batches.
  • This process will not block any other process on the page. Rendering the loaded items of each pagination page does not block items on other pages from being loaded. (We’ll learn about rendering later in the article).

Benchmark Test

🏁 What is a benchmark?
A benchmark is a test designed or used to establish a point of comparison for the performance or effectiveness of something, especially computer hardware or software.

Benchmark procedure:

  • Each time reported is the best of three runs.
  • Browser cache is always disabled in all tests to assure fair tests.
  • All Network tests are performed in three variations:
  • ~No throttling: Average connection speed of <span class="article_code">90 Mbps</span>.
  • ~Simulated Fast 3G: Average connection speed of <span class="article_code">1.5 Mbps</span>.
  • ~Simulated Slow 3G: Average connection speed of <span class="article_code">0.5 Mbps</span>.


  • Processor: Intel(R) Core(TM) i7-10750H CPU @ 2.60GHz 2.59 GHz
  • RAM: 32.0 GB
  • OS: Windows 11
  • Browser: Google Chrome v96.

Rendering paginated items

🧑‍💻 We’re going to get a little technical in the beginning of this section
Don’t worry if you don’t understand! Re-read it as many times as needed to better understand — or read it once and continue with the article.

DOM Education

The Document Object Model (DOM) is an interactive representation of the HTML of a page.

When the browser receives raw HTML code from the server, it parses the string and converts each HTML tag (<span class="article_code">&#60;h1></span>, <span class="article_code">&#60;div></span>, etc) and other elements like text or comments (<span class="article_code">&#60;!-- This is a comment --></span>) into a <span class="article_code">Node</span>. This <span class="article_code">Node </span>contains different properties depending on the element that represents it.

Once every element has been converted into a <span class="article_code">Node</span>, it proceeds to build the DOM Tree. The DOM Tree is used for managing the webpage in an efficient way by establishing the relationships between all nodes:

DOM tree
Visual representation of the DOM Tree.

Once the DOM Tree is constructed, the DOM is used by the browser to render any changes to the nodes. For example, adding or removing elements, changing styles or positions, etc. The DOM is publicly exposed so languages like JavaScript can access and interact with it.

Rendering Education

Any change that happens in the DOM is processed by the browser. The browser then triggers a series of actions to reflect the change on the user’s screen. This process is called rendering.

These are the series of actions that are taken to render:

  • Style: the browser processes each node of the DOM Tree to know what it should look like (based on the CSS and inline styles), constructing what is called the Render Tree.
  • Layout: once the styles have been computed, the browser determines the width, height and position of each node. If a node no longer fits in its previous position, a layout shift or reflow is caused, moving it to a new fitting position.
  • Paint: the browser paints every visual part of an element (text, colors, paddings...) to the screen.
  • Compositing: the browser ensures that all layers are rendered to the screen in the correct order.

A lot of processes happen to render items on the page. The bigger the DOM is, the more time and resources it takes to the browser to perform these render actions.

Excessive DOM Size Education

Websites with excessive DOM sizes (big sites with a lot of content) feel heavier and slower when navigating them. It is always recommended to keep a lean DOM size to improve a website’s performance.

DOM Size

The benchmark below defines the number of elements rendered on the page after loading all paginated items. We will explore a DOM size performance optimization in Attributes.

We compare two examples —

  1. DOM size with the optimizations we made in Attributes.
  2. DOM size if we didn't make these optimizations.
👉 It’s important to note that this DOM size benchmark is based on the Showcase 2k example which is used for testing in this article.
Comparison of the DOM size with and without Attributes performance optimizations.
DOM Size
Relative Size (less is better)
Without optimization

How does this DOM performance optimization work?

Attributes only renders items as they are needed. This means all items ‘behind’ the pagination are stored in browser memory rather than hidden with CSS display none.

When the user clicks “Next” in the Attributes pagination UI, the page 2 items are rendered on the page while Page 1 items are removed from the DOM and stored back in memory.

The same happens when filtering items, the items that no longer match the user's filters are removed from the DOM and all newly matching items are added (only if they weren't already present in the list).

This process of moving items back and forth from the DOM in real time is significantly more efficient than manipulating all of the items with CSS and is what leads to the large discrepancies in DOM sizes for our example project.

This Attributes performance optimization is crafted based on how popular frontend JavaScript frameworks like React work internally by using a Virtual DOM.

Don't Block The Main Thread

With Attributes, the DOM Tree will only contain the items which need to be displayed. If only 100 items of the 2000 total items are needed, Attributes will only render 100 items. If only 1 item of the 2000 is needed, Attributes will only the render 1 item.

Showing the amount of existing children nodes in the list, right after the page has loaded and after applying one filter.

The Collection List always has only the amount of child items of a single page rendered at any given time. If the list is filtered, only the amount of filtered items will be rendered on the page.

This approach results in a lighter page with a smaller DOM. This also frees up the browser from having to perform expensive computations every time a change occurs. Below we show a DOM Size Benchmark for a visual comparison of both product’s DOM Size.

Where are the rest of the Collection Items if they aren’t rendered on the page? Attributes keeps items in the browser’s memory instead of rendering them on the page. Keeping the items in memory is significantly more performant than rendering all items on the page.

✅ Aside from performance improvements, Attributes respects pseudo-classes like :nth-child(), as hidden items and are not rendered as children of the list.

As stated before, all of the actions performed by Attributes happen asynchronously. This means Attributes never blocks the main thread, which guarantees a fluid user experience that is never interrupted.

Below is the same dropdown thread blocking test we made above. This video shows the Attributes implementation and how the main thread is never blocked.

🏃 Note: Attributes loads all the items so fast that there’s not enough time to start clicking on the dropdown before the page finishes loading everything. This video shows a simulated slow network connection for the sake of the example.

Visual representation of how the main thread is never blocked while Attributes renders the loaded items on the page.

Let’s break down what’s happening in the video:

  1. The page loads and the user starts repeatedly clicking the dropdown.
  2. The dropdown is opening and closing as expected.
  3. The user is able to continue opening and closing the dropdown as items are loading on the page in the background.

The page is completely interactive during the whole process because Attributes is not running processes on the main thread. There are no UX problems with using the filter ui elements as items are loading.

Assets download

When a new element is added to the DOM, the browser checks to see if there are any additional assets that must be downloaded to correctly render that particular item. This includes <span class="article_code">css</span> styles, <span class="article_code">video</span> sources, <span class="article_code">iframe</span> sections or <span class="article_code">images</span>, and more.

Based on the size and type of these files, the overall performance can be affected.

Webflow’s native loading="lazy" attribute can help by deferring the loading of images which are not in the viewport. However, this feature is still unsupported by 27% of the browsers in the market including Safari, so we need to make sure that websites are still performant without lazy loading.

Our example project items all contain <span class="article_code">images</span>, so let’s see how Attributes handles them.


As we discussed before, Attributes only renders items on the page as needed, which means that the respective assets are only downloaded whenever they are displayed to the user.

Let’s look at the Attributes network requests without lazy loading:

Scrolling through all the images network requests in the Attributes example.

In the video you can see that only the initial page items’ assets are downloaded, as they are the only ones visible when the page loads. This approach means that only <span class="article_code">184</span> image requests are made, adding a total of <span class="article_code">13.7MB</span> of transferred data which is loaded in <span class="article_code">3s</span>.

Summary of the browser's image requests in Attributes' example.

Now that we see the immediate impact of how downloaded assets are handled we will explore what happens as we navigate to the second pagination page using Attributes.

Switching a page in the Attributes examples while inspecting the image network requests.

As you can see, page 2 items are rendered on the page only when the user navigates to page 2. Again, this limits the total number of images which need to be downloaded in any given instance which has huge performance implications. The automatic downloading of assets also happens when the user applies any filter or sorting to the list.

ℹ️ The amount of downloaded assets is directly tied to the amount of rendered items. The current example renders 100 new items, which is the amount of items per page that was defined when building it. The less rendered items, the less downloaded assets and vice versa.

Lighthouse Core Vitals

This benchmark shows the Core Vitals results for each setup using Lighthouse to perform all metrics.

Benchmark procedure:

  • Each time reported is the best of three runs.
  • Browser cache is always disabled in all tests to assure fair tests.
  • All Lighthouse tests are performed in two environments:
  • ~Desktop
  • ~Mobile
  • All tests are run through Google Chrome's built-in Lighthouse app in DevTools.


  • Processor: Intel(R) Core(TM) i7-10750H CPU @ 2.60GHz 2.59 GHz
  • RAM: 32.0 GB
  • OS: Windows 11
  • Browser: Google Chrome v96.


lighthouse core vitals
Overview of Lighthouse's results when loading Attributes' test website on Desktop.

Global Performance Score

The overall performance score calculated by Lighthouse (higher is better).

Best Run
Attributes (Desktop)
Attributes (Mobile)


The main keys to be assessed are:

  • Time to Interactive: How long does it take to the page to be fully interactive.
  • Total Blocking Time: For how long did some script on the page block the main thread.
  • Main Thread Work: How much processing did the main thread take during the page load.
  • JavaScript Execution Time: How long did the scripts run on the page.
Best Run
Time to Interactive
Time to Interactive
Total Blocking time
Total Blocking time
Main Thread Work
Main Thread Work
JavaScript Execution Time
JavaScript Execution Time

Filtering and sorting items

With Attributes, filtering happens on the the user’s device. Since everything happens on the frontend, there is no need to communicate back and forth with an external server. This also means that there is no API standing between a user and their filter requests.

This means:

  • Each filtering action starts immediately
  • Functionality is never affected by the users network connection*.
📌 *As long as Attributes have been loaded on the page, they will continue to work even if the users network connection is interrupted.

In our tests, loading a page and filtering a list of 2,000 items with Attributes took an average of <span class="article_code">0.5-3ms</span>.

Further optimizations when filtering

Attributes only applies changes to items that need to be changed. This means that Attributes will only update an item in the list if there is a specific reason to update it. This is similar to a Virtual DOM approach used by frameworks like React or Vue.

Let’s look at an example of this in practice.

In the example video below, the first three items in the list remain in the same place and untouched after filtering. Notice how the <span class="article_code">&#60;div></span> tags of the first three items are not flashing as the filter is run. This is because the first three items are completely omitted by the rendering action and are not changed. This additional efficiency allows Attributes to provide a lightning fast filter experience that doesn’t impact the website’s overall performance and page load speeds.

Filtering a large list of items with Attributes, only the required items are mutated.

The following video displays a user filtering a 2000 items list in real time:

Repeatedly filtering a 2000 items list with Attributes. The main thread is never blocked and the page remains always interactive.

The filtering of items appears to be in real-time without delay. As the user clicks a filter option, the items in the list update instantly.


We hope this article was both educational and interesting. While some of this content might be more technical than you are used to working with, we hope it helps you get a sense for the level of optimization that goes into a single product.

We work hard to provide you with the best experience when it comes to Webflow tools and we're excited to share this deeper technical knowledge with you through long-form content.

If you enjoyed and valued this content, please give us a shoutout online ro share this article with a friend. This is the best way to give the entire Finsweet team a big T


Happy filtering!


Sign up for future releases!

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.