How to radically optimize image rendering speed within repeating groups on

Also available in :


Victor Nihoul


Dec 5, 2022


🇬🇧 English

Let me start by showing you something, on the following video, both apps are made on Bubble.
Now, can you tell what we changed to have such a performance difference?


If you had been me a few years ago, you’ll probably answer in a very condescending way:

This is just basic repeating group pagination 😒

Well, it is not, but actually, a little bit, let me explain — and I promise I’ll give you everything you need to implement it!

Here’s what we’ll cover:

  1. Understanding what pre-fetching is
  2. Limitations study
  3. Building a repeating group with pagination
  4. Building our pre-fetcher system
  5. Re-integrating Imgix with custom HTML

In order words, we’ll upgrade our Bubble skills from this to that:


Understanding Prefetching

After seriously investigating repeating groups' behaviour last year at Flusk, and trying to implement image pre-fetching we discovered that the way Bubble processes images are not what we thought!

Pre-fet-cin what?

If you don’t know what pre-fetching is, that’s a way to reduce the perceived loading time of a repeating group using two tweaks:

  • Firstly: using pagination (you got me..) to only show a necessary amount of items in the repeating group in the first place, you’ve probably seen those “see more” buttons, that’s it. This avoids loading hundreds of database items if the user is only willing to access the first row.
  • Second: when the visible elements in the repeating group are loaded, we prepare/preload the next set of elements to be shown if the user clicks the “see more” button or scrolls down.
    At this stage, there should be no loading time at all when the user interacts.

Here’s how pre-fetching works on Bubble for visual learners:

Prefetching on
Considerable speed improvement happens when the user interacts.

Pre-fetching a repeating group can be achieved in many ways — the easiest is by using another repeating group, but we’ll get back to that later!

The Big Discovery

We then built our Bubble pre-fetcher, but… it didn’t work.
Usually, when your browser downloads the RG items when the user clicks on “see more” (blue bubble on the flowchart - in our case images), it knows that the items we pre-fetched are already in the cache, and can be used instead of downloading a new set of data.

But apparently, this was not the case with Bubble, and we found out why!
It all came from a specific way Bubble handles image elements.

This is the behaviour we expected to see happening:

Expected RG behaviour
Expected image load behaviour

Instead, this is what we observed:
Bubble sends a different version of the same image for each element because of its Imgix processing — a.k.a something to not send 1000px images when shown into a 10x10px element.

Actual image load behaviour in RG
The actual image load behaviour

And this behaviour happens even without using the:processed with Imgx suffix on your image.

Try it yourself -or check this: add an image element and save the URL of the image in the editor, then preview and compare with the URL of the image on your rendered/previewed page!

See? It’s different! There’s now something in front of the URL that looks like That’s the Imgix processing.

Now, how does that affects our pre-fetching system?
Let me get us back to my flowchart:

When Bubble pre-fetches the images in the pre-fetcher repeating group, it downloads a unique version X of the images.
But when my user interacts with the “see more” button, my original repeating group looks for version Y of the images.

Since it can’t find it, it downloads the images again when the button is clicked, which kills the entire purpose of the pre-fetching!

The fix to The Solution

To avoid this Imgix processing required for our pre-fetching system, we, therefore, need to use this simple trick by adding this parameter to our image URLs:


How to Build Pre-fetching in Bubble

We talked enough, let’s jump right in.

In order to be able to prefetch a repeating group’s content, we need two elements as mentioned above:

  1. A repeating group with pagination, which we will call “Main RG
  2. The pre-fetching system can be done using CSS, JS, and many other ways but for the sake of simplicity, we’ll just use another repeating group, which we will call “Fetcher RG”.

1. Main RG with pagination

Let’s start with a basic repeating group Main RG which will display a list of images from the database.

Somewhere, we’ll create a custom state that will hold the number of images to display, with a default value of 2 images.

We can now use this value to restrict the number of items to show in our Main RG

We almost have our pagination ready! The last step is to add a button for the user to be able to display more items.
This could also be done on scroll for an advanced UX design!

We’re done, you can check the result here in case you missed something.

So let’s first assess how fast our repeating group and images load:

First demo

2. Pre-fetching images with our Fetcher RG

The previous example was slow, right? I mean it’s Bubble, right?
Yes, but it’s Bubble for the newbies, and you’re about to become a pro of Bubble optimizing!

Now, we need a second repeating group, our Fetch RG, to display the same list and the same images.
Huun, why?

Because this repeating group will actually be hidden somewhere (1px/1px) and load the number of images plus the number of images to prefetch!
So in our case, it will load 2+2 images, so when the user clicks on “see more”, the next 2 images will be ready!

In the Fetcher RG, I add two more images to the list to fetch

Note: you can also prefetch more than 2 additional images if you expect the user to be scrolling fast

Once we’ve done that, we need to mind the issue we encountered earlier!

The Imgix processing is about to ruin our work because it will download different images from the Main RG and the Fetcher RG.

The same image (in yellow) is being downloaded twice because it has different URLs (version X and Y)

Remember the fix?
Simply add the following to your image element’s URL in both repeating groups:

Woohoo! We improved, and now our RG is loading faster whenever we click on the “see more” button.

Voilà, this is what it looks like, and here’s a record for you:

3. But what about our friend Imgix

Yes, you may have noticed, but we’re now completely skipping the native Imgix processing!
But that’s not a victory, because Imgix is actually our friend.
It allows fast image compression and resizing on the server side, and this must be part of our optimization strategy!

The following part is a little more complex and irritating

So how do we keep pre-fetching knowing that:

  • Bubble automatically adds Imgix processing to image elements
  • We can’t use Imagix because it breaks the pre-fetcher

Well, we don’t use image elements anymore 🥳

For the last example, we’re using HTML elements instead of image elements.

So let’s go ahead and replace our old image elements with freshly-designed HTML elements.
Inside, we still want to display an image, so we use the following code snippet:

<img src="[IMAGE]" width="100%" height="100%">

From there, we can then apply Imgix processing manually, by adding the Imgix server prefix, and the desired processing parameters at the end.
It looks like that:

<img src="[IMAGE_URL_ENCODED]?w=384&h=384&auto=compress&dpr=2&fit=max&q=50" width="100%" height="100%">

You just need to take care of formatting the URL as URL Encoded to ensure everything runs smoothly.

Here’s what it looks like for an image with dimensions provided, and quality to be set to 35%. The full list of Imgix parameters can be found here- and there are way more than in the native Bubble editor!

We then apply the same settings to our Fetcher RG to ensure they both download the same images while forcing the Imgix processing.

Hooray 🥳 We made it. The repeating group is now much faster.

See the result here or there:

Second demo


Bubble isn’t slow, and we need to get this idea out of people’s minds, it’s just built to be accessible to everyone, and while this presents limitations, we can always overcome them! 🙌

The technique used above can be applied for different use cases, not only images!
If you’re interested in another in-depth tutorial about pre-fetching any data type, feel free to comment on my forum post and I’ll send it to you as soon as it’s released!

In the meantime, here are some sweet options: