Builder SDK size

We’re using Builder.io in a client-side React app. We’ve noticed Builder is adding 145 KB total to our bundle size, which is definitely not insignificant. Is there a more optimized build available? Is there a way to not include React in the Builder SDK for people like us who are already including React?

Thanks for any insights.

Thanks for posting @sage930! Do you mind providing some more background on how you’re bundling @builder.io/react and measuring bundle size? When I look at the our ES5 module, it’s 95kb uncompressed, and 24kb after gzipping.

If you want more control over what’s loaded from the Builder library, you can use @builder.io/react/lite, which is 46kb uncompressed and 14kb gzipped. More details on how to use the lite package are here: https://github.com/BuilderIO/builder/blob/master/packages/react/lite.js

If you are using React 14+ then Builder shouldn’t bundle React again. If you are using a bundle analyzer/sourcemap explorer, I’d be happy to dig deeper into what Builder might be adding to your JS bundle.

I’m analyzing bundle sizes using https://github.com/danvk/source-map-explorer. This is on a create react app-based app (react version 16.8.2) where Builder is imported in the application root, like this:

// Initialize Builder.io
const { builder } = require(’@builder.io/react’)
builder.init(process.env.REACT_APP_BUILDER_IO_PUBLIC_API_KEY)
}

The 2 Builder packages we’re using are:
@builder.io/react”: “^1.1.13-9”,
@builder.io/widgets”: “^1.0.101”,

Good to know about the lite option!

Thanks for sending, that looks similar to what I was seeing in sourcemap-explorer in our SDK repo. While we will always strive to do better, I have a couple of quick ideas that might help in the short-term while we try to work additional performance improvements into our SDKs.

  • If you’re using a CDN that supports HTTP/2, you can compress assets with brotli. This should result in a 15-20% smaller payload size vs gzip. If you can’t use HTTP/2 (I’ve been there…) then Zopfli is a good alternative that should result in 5-10% smaller payloads vs gzip

  • Obviously payload size isn’t everything, there’s additional parse and execution time whenever you ship more JS to the browser. If you want to minimize your JS bundle-size then you could use our HTML API to fetch pre-rendered HTML and skip the SDK. Or, we have some customers using GatsbyJS and Builder webhooks to pre-render static pages whenever the Builder content changes. This takes a little more work to get setup, but will result in a super optimized user experience.

  • Lastly, I mentioned this in a previous comment, but you want a quick and potentially easy improvement, you can use our react-lite library to have more control over which pieces of Builder you’d like to include.

Hopefully this will spark some ideas on how to ensure that you can use Builder and ensure that you have a fast, user-friendly website! We’re always happy to brainstorm about performance optimizations :smiley:

In the future, we have some ideas on how to make our SDKs more tree-shakable (ensure no side-effects), as well an interesting approach to render components in a super-lightweight JSX wrapper. Please let us know if you have any additional thoughts!

Thanks! Is the lite option documented anywhere? I’d like to know:

  1. What its limitations are, other than needing to explicitly specify what Builder components we want enabled. Does the lite option still work if we’re importing some custom components of our own (Ex. a carousel component)?
  2. How to use it under our current integration (just use the code from https://github.com/BuilderIO/builder/blob/master/packages/react/lite.js in our application root? Do we still need to call Builder.init()?)
  3. What use this in conjunction with customInsertMenu means. Is that something we’d set up in our application root as well, after we initialize Builder?
  1. The lite option should work with custom components. The main difference is that you need to specifically add any built-in components you want to use or they won’t show up.
  2. To use the lite package, you change all your imports from @buidler/react to @builder/react/lite and then import any of the built-in components that you want to use:
    // Change all imports from '@builder.io/react' to '@builder.io/react/lite'
    import { BuilderComponent } from '@builder.io/react/lite';
  
    // Import only what built-in components you like
    import '@builder.io/react/dist/lib/src/blocks/Button';
    import '@builder.io/react/dist/lib/src/blocks/Columns';
  1. So by default, the insert menu in the editor is going to show all the built-in block types that can be added (Image, Text, Carousel, etc). If you are using the lite version to not include some of these blocks in the SDK, then you won’t want them to show up in the editor. That’s why we recommend using a customInsertMenu to ensure that you don’t try to add blocks to your page that you aren’t able to render. Here’s an example of manually registering components in the insert menu: https://github.com/BuilderIO/builder/blob/071119913aa6ea99947ead868893d340678b2482/examples/react-design-system/src/builder-settings.js#L23-L116
1 Like

Thanks for that. A few more questions since this “lite” option isn’t documented yet:

  1. I noticed that not all components we use are available for import at @builder.io/react/dist/lib/src. Should we be importing Carousel from @builder.io/widgets/dist/lib/components/? What about Box? I don’t see that one anywhere.

  2. How should we be importing withBuilder which we use in our custom components that we import into Builder? Do we still need to import that from @builder.io/react?

  3. We had been using import '@builder.io/widgets. I assume we no longer need that, since we’re directly importing components that we’re using. Is that the case?

If you are using a number of the built-in components, then the lite option might be more trouble than it’s worth. In other words, importing the built-in components limits the performance benefit while also adding some complexity to your integration.

That being said, I’ll try to answer you questions about how to use the lite library:

  1. Right now, if you want to use anything from @builder.io/widgets you would likely need to import the full widgets library. One way to potentially solve this is to fork (copy) the widgets/components and register them as custom components in your private repo. Box is not a widget or component, it’s basically an empty element like:
{
  name: 'Box',
  item: { '@type': '@builder.io/sdk:Element'  }
}
  1. You can use Builder.registerComponent, an alias for withBuilder
  2. You can still use that import, or as mentioned above, clone/copy the widget and import them individually.

If want to reimplement Box, you would need to use register it with a custom insert menu like in this example:

Finally, just wanted to say thanks again for the feedback and effort to optimize your Builder integration! I’ll make sure we have additional performance optimizations on our roadmap as well as making it easier to use the “lite” library.

Hey @sage930 - wanted to followup with another trick that may be useful here to keep your bundle size down and only load the Builder JS when a Builder component is actually displayed

A useful trick here would be to lazy load just the BuilderComponent, so for example instead of doing

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

Instead create an async component for this, e.g. with Loadable or suspense and use that instead, e.g.

With suspense:

// e.g. components/async-builder-component.jsx

const LazyBuilderComponent = React.lazy(() => import('@builder.io/react').then(res => res.BuilderComponent));

const AsyncBuilderComponent = (props) => (
  // Show a spinner while the profile is loading
  <React.Suspense fallback={<Spinner />}>
    <BuilderComponent {...props} />
  </React.Suspense>
)

export default AsyncBuilderComponent;

Or with loadable:

// e.g. components/async-builder-component.jsx
import Loadable from 'react-loadable';

const AsyncBuilderComponent = Loadable({
  loader: () => import('@builder.io/react').then(res => res.BuilderComponent),
  loading: Spinner,
});

export default AsyncBuilderComponent;

Then replace anywhere in your code you have

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

to

import BuilderComponent from './components/async-builder-component'

And now all your current code will work as needed, and all Builder SDK code will be removed from from your current bundle and split into a separate one that is only fetched asynchronously when Builder code will display, after the rest of your code has loaded

Thanks for this. I was able to get lazy loading of <BuilderComponent> working with a slight modification of your code:

const LazyBuilderComponent = lazy(() => import('@builder.io/react')
  .then(module => ({ default: module.BuilderComponent }))

However, I’m still seeing Builder showing up in our main vendor JS bundle file :frowning:

We do code splitting per route, so our “catch-all” page which renders Builder pages when there’s a URL match is lazy loaded. I’m wondering if that’s getting in the way here. We also use CRA (not ejected), as another data point.

Hey @sage930 - apologies I missed your reply somehow. If Builder is still in your bundle then it must be imported/referenced somewhere without using a dynamic import. Is there any chance it lives anywhere else in your code? One possible culprit would be registering your components, like

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

Builder.registerComponent(...)

One workaround there is to make sure those components are loaded lazily as well, or separate the Builder.registerComponent(…) into a separate file and import that, e.g.

// src/builder-lazy.js

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

// Register your components (or put each Builder.registerComponent in its own file and only import those files here)
Builder.registerComponent(...);
Builder.registerComponent(...);

export default BuilderComponent;

Then lazy import this file instead of @builder.io/react directly

const LazyBuilderComponent = lazy(() => import('./src/builder-lazy')

That way any other call to Builder, like Builder.registerComponent, is only in this lazy bundle as needed and not in your main one

Our v2 React SDK (which does not have all React v1 SDK features, but is much more lightweight) is now out in Beta: builder/README.md at main · BuilderIO/builder · GitHub

example of how to use it: builder/examples/react-v2 at main · BuilderIO/builder · GitHub

See High bundle - Builder io React · Issue #1179 · BuilderIO/builder · GitHub for more context

I am using React 18, but it will still bundle React. Is there any way to improve this?

We are still in the process of fully documenting our gen2 React SDK, but it is worth noting that it is now in v1! If you are concerned with high bundle size, the Gen2 React SDK is your best bet.

its total bundle size is 25kb, with zero dependencies: