When a storefront app slows a store down, it is usually for one of three reasons: heavy bundles, an API call to the app's server on every page view, or global CSS that fights the theme. Rubik Combined Listings Swatch was built to avoid all three. This page lists what the app actually loads, how it loads it, where the time goes, and how to measure it on your own store. The numbers about the app on this page come from measurements, not estimates, and the page says where and when the main ones were taken.
Built for Shopify. The app holds Shopify's Built for Shopify badge. Among its requirements, Shopify states that an app "must not reduce the storefront Lighthouse performance score by more than ten points" (requirements), and it measures the app's own admin against Core Web Vitals thresholds (Largest Contentful Paint 2.5 s, Cumulative Layout Shift 0.1, Interaction to Next Paint 200 ms, at the 75th percentile).
Measured on 26 August 2026 on the public demo store (combinedlistings.rubikdemo.com, Horizon theme), reading the browser's resource timing for the assets served by Shopify's CDN. "Compressed" is the Brotli size Shopify's CDN serves (Brotli and gzip are the two compression formats in common use; for the main files gzip is 6 to 7% larger); the two inline figures are Brotli too. None of these files is render-blocking, meaning none has to finish downloading before the browser can draw the page.
| Asset | Compressed | Uncompressed | How it loads |
|---|---|---|---|
Main script (rubik-combined-listings.min.js) |
23.3 KB | 96 KB | defer: downloads in parallel with the page, runs after the HTML is parsed, never blocks rendering |
Stylesheet (rubik-combined-listings.css) |
8.1 KB | 33.6 KB | Downloaded in the background by a small inline script and applied only inside the swatch element, so it is not a page stylesheet and cannot hold up the theme's first paint |
Theme configuration (theme_<theme>.js) |
under 5 KB (most themes under 1 KB; Horizon, the largest, 4.8 KB) | 0.8 to 17 KB | Only the file for the store's own theme; the other 175 theme files are never requested |
Carousel library (rubik-swiper-bundle.min.js) |
40.2 KB | 155 KB | Loaded only where a carousel layout is used: on product pages whose groups use it, as an async tag; elsewhere, fetched on demand the first time a carousel renders. Never on the critical path. Namespaced (RubikSwiper) so it does not overwrite a theme's own Swiper global |
| Inline embed (settings, theme map, icon font, inline start-up script) | ~5 KB | 12.2 KB | Part of the page HTML; not a separate request |
| Swatch data | ~2 KB (one group of five products) | 17 KB | Part of the product page HTML that Shopify renders with the page; not a separate request |
Everything the app loads on a typical page adds up to about 36 KB compressed (main script, stylesheet, theme configuration). The 40 KB carousel library is added only on pages that render a carousel layout.
What does not load: no framework, no jQuery, no polyfills, no external web fonts (the carousel arrows use a 2 KB icon font embedded in the page), no third-party scripts, no tracking pixels, and it sets no cookies. The main script is plain JavaScript with a single dependency, the carousel library above.
<body>. None of the app's code is in the <head> of the served HTML, so the theme's critical CSS, fonts, and hero images are always discovered first.defer). It parses the swatch JSON already in the page, finds the injection point for the store's theme, and builds the swatch row inside a shadow root (a walled-off part of the page whose styles the theme's CSS does not override). It applies the stylesheet from step 2 in the same step: normally it has already arrived, and if not, the row stays hidden until it does, so there is never a flash of unstyled content. Then it inserts the finished element into the page in a single operation.srcset ladder with a sizes hint so every browser fetches the smallest file that is still sharp at its own pixel density (details under Rendering choices below).Step 4 is the app's rendering work, and it measures about 3 ms end to end (2.6 ms in the July 2026 measurement on our development store, Horizon product page). The largest parts are building the swatch elements (0.9 ms), resolving the group's visual settings (0.2 ms), and parsing the JSON (0.1 ms); selector matching and insertion take well under 0.1 ms each. Browsers flag tasks over 50 ms as "long tasks" that hurt interaction responsiveness; the app's rendering is less than a tenth of that.
A common performance mistake in storefront apps is fetching data from the app's own server on every page view: an extra connection to a server that may be far from the shopper, and a hard dependency on that server being up. Rubik Combined Listings Swatch has none of that.
navigator.sendBeacon) to the store's own domain through a Shopify app proxy, which forwards them to a small edge service we operate. Clicks are batched for up to five seconds and never delay a click or a navigation, and swatches keep working if that service is down.In August 2026 we measured the cold-load path on the demo store's Horizon product page, hard-reloaded so no app asset is cached, timing the moment the swatch row becomes visible. The store is on the app's default settings, with every Performance optimization turned off, which is how most stores run. Across repeated runs the swatch row appeared a median of about 1,280 ms after navigation start on this image-heavy page. Absolute timings scale with the shopper's connection and the store's own weight; what stays constant, and what the app is built around, is how that time divides:
| Share | Phase | Whose time it is |
|---|---|---|
| ~11% (0 to ~145 ms) | The store's own HTML arriving and being parsed. Because the app sits at the very end of the document, the browser cannot begin fetching the app's assets until the theme's HTML and the app embeds ahead of it have been received. | The store's page weight |
| ~88% (~145 to ~1,275 ms) | Fetching the app's assets. The main script and the single theme-configuration file download in parallel, and the render waits for both. Almost none of this is size: the script is 23 KB and its download took about 30 ms. Nearly all of it is request queueing, about 1,050 ms on this page, the app's low-priority assets waiting their turn behind the roughly 250 theme and product images the page also loads. | Browser scheduling and network |
| ~0.2% (~1,275 to ~1,278 ms) | Everything the app computes: parse, match, build, insert. | The app |
That queue is the one part a store can shorten. Turning on the optional High priority swatch loading setting moved the app's assets toward the front of the queue: measured on the same page, the wait dropped from about 1,050 ms to about 250 ms and the swatch row appeared at about 550 ms instead of 1,280 ms, with no change to the bytes downloaded. On a warm reload, with every asset served from the browser's cache, the row appears within about 20 ms of the browser reaching the app embed. Two conclusions shaped the app. Shrinking the bundle further would barely move any of these numbers, because the bytes are already a small, fast download and the wait is queueing, not size. The one lever that helps is request priority, which is why the app offers the opt-in High priority swatch loading setting, marked Recommended, rather than forcing it on every store.
resize event (never more than one per burst of cards) so the theme can adjust. Apart from those insertions (and the small adjustment when a row limited to one line collapses its overflow into a "+n" button right after it appears), the app introduces no layout shifts unless a store has asked us to hide theme elements.loading="lazy" and decoding="async", and every one carries a srcset ladder (100 to 500 px by default) with a sizes hint taken from the swatch's configured size, so each browser fetches the smallest file that is sharp at its own pixel density: a 32 px card swatch on a laptop is a 100 px file of 1 to 3 KB on our demo store, a 60 px swatch on a 3x phone gets the 200 px file, and nobody downloads a 4,000 px original for a 40 px swatch. The store's swatch image width setting (500 px by default) is the ceiling of that ladder, and the main-image hover preview uses 1,200 px images. Hover previews never fetch images at render time: the product-card hover swap ("Change product card image on hover", on by default) and the product-page main image preview ("Change main image on hover", off by default) fetch the linked products' images at low priority only when a shopper's pointer first enters that card or swatch row, and never on touch devices, so cards nobody hovers cost nothing.contain: inline-size, where the browser supports it), which limits the layout work a carousel can trigger outside the swatch row.The app ships placement rules for nearly 200 theme listings and page builders; the rest of the more than 350 themes it supports (supported themes) use the default selectors built into the main script, or app blocks. Those rules are 176 separate configuration files, about 1.4 KB each on average (many themes share one), and a store loads at most one of them: the theme is resolved in the inline script from a map that ships with the embed, so the right file is requested immediately rather than after the main script runs. Shipping every configuration to every store would add about 250 KB uncompressed to each page view, of which a store would use a single file.
The app is designed so that a bad network or a stalled request does not turn it into a page-weight problem.
These are off by default because each one is a trade-off that depends on the store. You will find them under Settings > Performance optimizations in the app; all but Prerender are marked Recommended there.
| Setting | What it does | When to enable it |
|---|---|---|
| High priority swatch loading | Adds fetchpriority="high" to the main script and theme configuration so the browser schedules them alongside critical resources instead of behind gallery images. Targets the request queueing we measured on the demo store's product page: about 1,050 ms with the setting off, about 250 ms with it on. |
Stores whose product pages load many images or apps and where swatches appear noticeably after the price. |
| Prefetch linked product on hover and Prerender linked product on hover | Warms the linked product page when a shopper hovers or touches a swatch, so the navigation after the click is faster (Prefetch) or near-instant (Prerender). | Stores where shoppers browse many colors of one product. Prerender is Chromium-only, so keep Prefetch on as well to give other browsers a speed-up. |
| Preserve scroll position on swatch click | Restores the shopper's scroll position after navigating between the products in a group, so the swatch row stays where their thumb was. | Mobile-heavy stores with long product pages. |
rubik. You will see the main script, the stylesheet, and the theme configuration (theme_<theme>.js, served from the same path), plus the carousel library only on pages with a carousel layout, with roughly the compressed sizes shown above (the panel adds response headers), all from Shopify's CDN (cdn.shopify.com). The demo store runs on the app's default settings, with the Performance optimizations off, so the main script loads at normal priority; enabling High priority swatch loading adds fetchpriority="high" to it.?rcl_debug=true to any product or collection URL and open the console. The app logs what it detected (template, theme, configuration), any cards it excluded, and each swatch row it injected; enable console timestamps to see when.It is built not to, and the measured numbers bear that out: about 36 KB of compressed assets on a typical page (76 KB where a carousel layout is used), none of them render-blocking, swatch data rendered into the page by Shopify, and about 3 ms of rendering work. Built for Shopify certification requires that an app does not reduce the storefront Lighthouse performance score by more than ten points.
No. Swatches render with no request to our servers: the assets come from Shopify's CDN and the swatch data comes from the page itself or from your store's own domain. The one thing the storefront code sends anywhere is a fire-and-forget click-analytics beacon, posted to your own domain after a click and relayed by Shopify's app proxy. If our servers were unreachable, swatches would keep working unchanged; only click analytics would pause. (With the optional Prerender linked product on hover setting on, the browser also loads the linked product page early, scripts included.)
It is designed to stay out of their way. Scripts are deferred or async and placed at the end of the body, so they do not block the Largest Contentful Paint. The app's styles are isolated in a shadow root, and swatch images are lazy-loaded and served at the right size for each screen. The only layout shifts it introduces are the swatch rows themselves being inserted (once on a product page as soon as the deferred script and the theme configuration have run, and per product card on collection pages when that card's data is available), a small adjustment when a row limited to one line collapses its overflow into a "+n" button, and the shift from hiding any theme elements you ask us to hide. The rendering work is a few milliseconds, far below the 50 ms long-task threshold that affects interaction responsiveness.
23 KB compressed for the main script, plus the configuration for your theme (under 5 KB) and about 5 KB of inline code that is part of the page HTML. A 40 KB compressed carousel library loads only on pages that render a carousel layout.
Not on checkout: Shopify runs no theme app embeds there, and the same is true of the new customer accounts pages. The app embed is part of your Online Store theme, so its small script loads with theme pages (product, collection, home, search, cart, blog, and content pages) and does its work only where there is a product or product cards to enhance; elsewhere it finds nothing to do. When the app is turned off in its settings, the embed ships no script or stylesheet; only the small data block of any app block you added to your theme remains until you remove that block.
Not on your storefront: the storefront code sets no cookies, loads no third-party scripts, and stores nothing in the browser by default; the optional scroll position feature uses sessionStorage for a single page transition. Swatch click analytics send aggregated click counts (placement, meaning product page or product card, product ID and title, option name and value, group, whether the product was in stock, swatch position and type, device type) to your store's own domain up to five seconds after a click, with no shopper identifiers in the payload. Cookies used by the app's admin interface are described in the privacy policy.
The measurements on this page show that swatches appear when the page's own HTML and images allow them to; on heavy stores the app is waiting on the page, not the other way round. The High priority swatch loading setting helps the app's assets get ahead of non-critical images, and the Prefetch and Prerender options make moving between the products in a group faster even when the pages themselves are heavy.