BUILDER

Blog

Home

Resources

Blog

Forum

Github

Login

Signup

×

Visual CMS

Drag-and-drop visual editor and headless CMS for any tech stack

Theme Studio for Shopify

Build and optimize your Shopify-hosted storefront, no coding required

Resources

Blog

Get StartedLogin

This page was made in Builder!

‹ Back to blog

High performance no-code

By Steve Sewell

At Builder, we are performance-obsessed. We have to be. In e-commerce, lost seconds equals lost sales. So, how do we generate code to meet our rigorous performance standards? Read below to find out.

Optimize Images

Images are often the biggest contributor to total bytes downloaded on a page. This can cause slow experiences for visitors to your website, especially those visiting on mobile devices. Luckily, there is a lot that can be done to optimize images and, interestingly, our visual no-code tool can optimize images even more than many code frameworks themselves.

Webp

One excellent technique for optimizing images is to use next-gen formats like webp to serve the image. Unfortunately the webp format is not supported by all web browsers, meaning that we can't just reformat all images to be webp and call it a day. However, the good news is that by using the HTML picture tag we can serve the correct optimized format for every web browser, falling back to other formats for those that don't support webp.

<picture>
  <source srcset="..." type="image/webp">
  <img srcset="...">
</picture>

Srcset and dynamic images API

Next, using srcset is imperative. It is used to tell the browser what size image it should download (e.g. small sized for a mobile device). In combination with a dynamic image delivery API, you can ensure that you never load an image larger than needed. Using srcset also ensures that the best image is chosen not just by the current device's size, but as well as their pixel density.

<picture>
  <source 
    srcset="
      https://cdn.builder.io/api/v1/image/...?width=100 100w, 
      https://cdn.builder.io/api/v1/image/...?width=200 200w, 
     ..." 
    type="...">
  <img srcset="...">
</picture>

Generated sizes attribute

This is a big one most developers and code frameworks miss. Unless you specify the sizes attribute on an image tag, the browser will assume your image is the entire width of the page. Yikes! That is often not the case, and can lead fetching of excessively large images.

Creating the proper sizes attribute for every image on your site can be a tedious and time consuming process. Luckily, Builder analyzes your image as it relates to the layout of your page and determines the exact sizing of your image for all device sizes, automatically generating the optimal sizes attribute.

<picture>
  <source srcset="..."  type="...">
  <img srcset="..." sizes="(max-width: 400px) 95vw, (max-width: 900px) 50vw, 800px">
</picture>

Below the fold lazy loading

Lazy loading is another critical image optimization technique. There is no reason a visitor should download all the images of a page right when it loads. They might not even scroll far enough to see them all, so why waste the bandwidth? We should always wait until someone actually needs to see the image before loading it in their browser.

Traditionally people would use JavaScript scroll handlers and expensive layout calculations to determine when to load an image, which comes with their own set of problems. Luckily, modern browsers allow for the use of the IntersectionObserver API, which makes lazy loading images much more performant than it used to be.

const observer = new IntersectionObserver(callback, {
    root: document.body,
    rootMargin: '0px',
    threshold: 0 // display immediately when even 1px is visible
});

On top of that, you only want the below the fold images lazy loading. The content immediately visible on the page when landing will have a much better perceived performance if it loads immediately

Builder accomplishes this with the same responsive page layout analysis described in the sizes section above - we automatically flag which images are below the page fold for each device and need to be lazy loaded.

Code splitting

When a page loads, you only want the bare minimum CSS, HTML, and JS needed to load the content.

By contrast, many applications have global CSS and JS that loads regardless of if any content would use this CSs or JS.

Builder, by contrast, aggressively splits code and ensures when loading a page or component that only the absolute needed CSS, JS, and HTML is included.

<style>
  .box { /* ... */ }
  .text { /* ... */ }
  .button { /* ... */ }
  .h1 { /* ... */ }
  .h2 { /* ... */ }
  .h3 { /* ... */ }
  .h4 { /* ... */ }
</style>

<div class="box">
  Hello world!
</div>

<style>
  .box { /* ... */ }
</style>

<div class="box">
  Hello world!
</div>

Compression

Builder also serves minified, gzipped, and deduped CSS, JS, and HTML.

The deduping part is particularly interesting, as Builder has no notion of CSS declaration order dictating style precedence. This means we can more aggressively combine styles.

.my-box {
  display: flex;
  flex-direction: column;
  align-items: center;
  /* ... */
  font-size: 15;
}

.my-modal {
  display: flex;
  flex-direction: column;
  align-items: center;
  /* ... */
}

.my-box, .my-modal {
  display: flex;
  flex-direction: column;
  align-items: center;
  /* ... */
}

.my-box {
  font-size: 15;
}

The above example may look simple, but when taking into account large amounts of styles, the savings can be very significant.

Pre-rendering

All Builder content is fully server side serializable to HTML. This means we can pre-render all content to optimized HTML, and remove all blocking scripts or styles. This way we can deliver content as very lightweight HTML and lazily load any additional JS, CSS, etc for interactive elements and hydrate those in after initial load.

We do this in a modern style similar to Gatsby, Next.js, and Nuxt - and can work within any of those frameworks (and others!) natively as well.

<!-- Blocking JS requiring download to make page visible -->
<script src="..."></script>
<!-- Blocking CSS requiring download to make page visible -->
<link rel="stylesheet" href="...">
<div>
  <button>Click me!</button>
  <div class="my-modal">
    <!-- Modal contents -->
  </div>
</div>

<style>/* Minimal above fold CSS */ </style>
<div>
  <button>Click me!</button>
</div>
<!-- Non-blocking lazy script -->
<script async src="..."></script>

Server-side segmentation

One very powerful feature of Builder is targeting and segmentation - showing different content to different audiences. We support this server-side optimized for various frameworks and platforms. For instance, for Shopify we generate code like:

{% if !customer.accepts_markering %}
  {% comment %} Sign up for newsletter modal {% endcomment %}
  {% render 'content.abc123.builder' %}
{% elsif now > 1601672082 %}
  {% comment %} Date targeted promotion {% endcomment %}
  {% render 'content.abc123.builder' %}
{% else %}
  {% comment %} General content {% endcomment %}
  {% render 'content.abc123.builder' %}
{% endif %}

Static generated A/B testing

Another significant performance win we achieve is server-side A/B testing. We are able to do this because Builder can control whole sections of content, including using your custom code components.

As opposed to traditional A/B testing tools that block content load, making performance suffer, and jam in content with javascript, Builder generates each variation server-side, and has a tiny inline script to dice out each variation to it's appropriate traffic.

Thanks to gzipping, all overlapping content deflates away in the compression serving a very optimized and fast page with little added weight, regardless of the amount of test groups.

This is supported for all frameworks, including static frameworks like Gatsby and Nuxt.

Yes, you read that right. Dynamic content built statically: different users seeing different content on a static site.

<div data-builder-variation-id="...">
  <!-- Default variation -->
</div>
<template>
  <div data-builder-variation-id="...">
    <!-- Test group variation -->
  </div>
</template>
<script>
 // Minimal generated inline script to check orput the user in a test group, and show just the proper content
</script>

Native framework integrations

Builder works natively with many frameworks, and support all with our various APIs and SDKs.

You can even use your code components in the visual editor, and even limit editing to require only using those to enforce design systems and standards.

React

Shopify

HTML API

Angular

Webcomponents

Vue

import { BuilderComponent } from '@builder.io/react'

export const Header = () => <>
  <BuilderComponent model="my-model" />
  {/* The rest of your header */}
</>

Also see our guides for Next.js and Gatsby

Dynamic edge delivery

We also serve code dynamically over our various APIs. This ensures code is only loaded as needed, and can likewise be segmented and A/B tested using edge compute (serving from an edge CDN but different visitors get different results)

Additionally, we use stale-while-revalidate caching at the edge to make sure average response times are under 50ms, while still being fresh up to the minute!

Conclusion

No-code tools don't have to mean slow-code tools. In fact, the very opposite can be true. Defining code in a declarative JSON-based format with a visual editor and framework-specific optimizing code renders and compilers can actually mean significant performance gains.

And did we mention, all our our SDKs are open source?

Do you have ideas on how to optimize our code even more? Send us a pull request and let's collaborate!

Or if you want to work on the future of visual software development, check out our open positions.

Read more on the blog
Builder raises $3.25M to power no-code for e-commerce
The evolution of headless commerce

BUILDER

Product

Visual CMS

Theme Studio for Shopify

Sign up

Login

Featured Integrations

React

Angular

Next.js

Gatsby

Get In Touch

Chat With Us

Twitter

Linkedin

Careers

© 2020 Builder.io, Inc.

Security

Privacy Policy

Terms of Service