Meteor bundle is huge

Hey guys so I’ve been trying to optimize my app’s load time. I’m even using dynamic imports in flowrouter to code split on routes. But even then the initial bundle size is 1.06 MB.
Even if i remove import statements of all components and just import home page (which is 99% static html, about 200 lines only) even then it gets down to 1 MB only.
I’m using meteor bundle analyze to check initial bundle size.

There’s jQuery that’s being used as dependency to some packages.

Is it just not possible to bring initial bundle size to below 300kB? The load time of my app is huge and TTI is about 9 seconds even with code splitting.

2 Likes

9s to download 1mb? that’s around 117kb/s, average global download speed is 25MB/s, is this the average connection of your target region?

With regard to bundle size, Meteor minimal is 47.9kb (so I think technically you can drop below 300kb, although I’ve never tried this bundle, maybe it’s just http server?) and full blaze app is 411kb.

But I personally think 1MB is not really much by today standards, if you use react & react-dom then 150kb (half of your 300kb target is already gone). I think Blaze has hard dependency on jQuery (84.5kb) so you can’t get rid of of it.

The discourse forum we are using here loads 4.3MB per view, so it would take you roughly 38s to view this post!..are you sure there is nothing else blocking the rendering?

[Correction: average global download speed 25Mbps, not MBps. 25Mbps is ~3MB/s, thanks @cereal]

2 Likes

The global average download speed is 25Mbps, not MBps. 25Mbps is ~3MB/s.

1 Like

Ah, yes make more sense thanks for the correction, that’s on mobile, which is still fast enough.

The argument still hold, the 1.06MB bundle should be downloaded globally on average in less than a second or am I missing something else?

I’m sorry i should’ve provided more data.
Here’s a pic of running bundle analyzer, Initial Client bundle is 1.06 MB but, the network tab shows 1.2MB which is cuz of Google Fonts and analytics.

Here’s a pic of lighthouse audit for Mobile device. TTI is 9.1s with a horrible performance score :frowning:

I currently have only 1 route and Home itself is very simple page, with 3-4 images and just static HTML with a call to Meteor.userId? Flowrouter.go('dashboard') : Flowrouter.go('login')

import '../imports/stylesheets/main.scss' 

const exposed = FlowRouter.group({
  name: 'public routes'
})

exposed.route('/', {
  name: 'home',
  async action() {
    let Header = import('../imports/components/base/header.jsx')
    let HomePage = import('../imports/components/pages/home.jsx')
    let Footer = import('../imports/components/base/footer.jsx')

    HomePage = await HomePage
    Header = await Header
    Footer = await Footer

    Header = Header.default
    HomePage = HomePage.default
    Footer = Footer.default

    mount(PageLayout, {
      header: <Header />,
      content: <HomePage />,
      footer: <Footer />
    })
  }
})

Here’s my Package.Json Analysis

And these are the Meteor Packages

meteor-base@1.1.0       
mongo@1.1.18              
reactive-var@1.0.11      
tracker@1.1.3               

standard-minifier-js@2.1.0 
es5-shim@4.6.15               
ecmascript@0.8.0              
shell-server@0.2.3            

kadira:flow-router
aldeed:collection2-core@2.0.0
react-meteor-data
chrismbeckett:toastr
twbs:bootstrap
accounts-password@1.3.6
meteorhacks:subs-manager
todda00:friendly-slugs
seba:minifiers-autoprefixer
natestrauser:font-awesome
zimme:active-route
dynamic-import
session
email
matb33:collection-hooks
edgee:slingshot
kadira:dochead
fourseven:scss
1 Like

The first meaningful paint can be improved with SSR. Looking at your resources I can see some external JS (checkout, chat etc) those could be delaying Time To Interactive since the browser is parsing those scripts.

As I suspected the 9s you mentioned has nothing to do wth bundle size, but more with the external JS dependencies being loaded and the lack of SSR. If you don’t want to use SSR, you need to show loader and decrease the loaded JS so it’s around 4s Time To Interactive otherwise you can SSR and the first meaningful paint would be under 2s but you still need to optimize the TTI, 9s is too long.

this bundle size is not gzipped. The bundle will usually be served gzipped und is therefore much smaller. You should see that on the network tab, but on your screenshot above, the bundle is not visible (or i am blind)

1 Like

This can become something like

const [Header, HomePage, Footer] = await Promise.all([
  import('../imports/components/base/header.jsx'),
  import('../imports/components/pages/home.jsx'),
  import('../imports/components/base/footer.jsx')
])

This gets them to download at the same time. Right now, you have a long critical path where you wait for each component to load before asking for the next one. I bet this will speed up your paint a lot.

3 Likes

Is that right? He’s calling each import function sequentially, but not awaiting any of them until after the import has been called 3 times. I’m not really sure what that would do.

Yeah at best it would only save a few turns of the event loop (since await always pauses and queues a microtask)

you were right, bundle after gzip is just 307kB :smiley:

thanks, removing external sources did help with TTI down to 6.4 from 9s, but, the speed index is horrible :sweat_smile: .
Even if i used defer TTI wasn’t improving so had to remove external sources.
Is there some way to download them without blocking homepage render?

Also, i’m very new to SSR with flowrouter, i checked server-render package, will that work?

I personally wouldn’t worry about that speed index number and focus on optimizing the right things. If premature optimization is the root of all evil, then prematurely optimizing the wrong thing is the devil himself.

You focused on the bundle size which was not your issue, 1MB is not large let alone huge, it’ll be gziped and cached. Thus what you really need to optimize are the First Meaningful & Contentful Paints, those have a huge impact on the perceived initial load time by the user.

Here is a screenshot of an optimized meteor app audit:

You can see that what has been optimized are those two numbers, it’s really big app but the initial paint takes only 0.8s. This was achieved via SSRing the main page. As far as the user is concerned, the site is blazing fast. To get that fast paint, you need remove anything not critical from the main rendering path (any JS, CSS that could block the rendering), you can either remove them, reduce them or load them later. If you want them to not-block then they need to be loaded at the the end of the body, but do you really need the checkout scripts on initial render?

For SSR, read the Meteor guide carefully, you can SSR your main page regardless of the router but it’s not that trivial and require some work. Your other option is to accept the 4s delay and show loading indicator.

I hope that helps!

4 Likes

yes that helps, thanks a lot for being patient :smiley:
I’ll look into SSR and only pull homepage CSS, JS, that way it should get much better

1 Like

@alawi - Thank you so much for your advice in this thread!!! You were exactly right in judging how to configure Meteor to pass the Lighthouse audit tests. I had been putting off SSR for probably two years or more now, and found myself needing to do a performance audit before shipping a deliverable. Implementing SSR was exactly what was needed to get those performance metrics back in the green.

Also, note to anybody else running through the Lighthouse Audit… you will want to minimize your css, as it will decrease your bundle size by 20% to 30%. So be sure to use the -production flag.

meteor run --settings settings.json --production
4 Likes

To give my 2 cents here, I just wrote an article on dynamic import in packages. This may still be an underestimated issue but many packages just add all their files and unnecessarily fill up the initial bundle size.

However, rewriting packages using dynamic imports is not that big issue: https://dev.to/jankapunkt/lightweight-meteor-packages-with-conditional-dynamic-imports-1d51

3 Likes