Suggestions for making my app load faster

Meteor is really amazing tool when it comes to building a fullstack app. I’m really thankful to you guys for building it.

The project that I’m doing called Dynamic learning has been entirely build on top of Meteor
https://www.dynamiclearning.io/

But the site takes too much time to load. I want to somehow minimize the loading time. Do you guys have any suggestions to minimize the loading time?

2 Likes

There are several things you can do, first one of which is identify why your app is slow.
I can tell you right away it is because your app is hyuuuuge. 14MB is gigantic.

So, what I would do:

  1. use bundle visualizer to identify which part of your app is so big.
  2. Either remove, replace, or dynamically load the big parts only when needed. Target an initial bundle in the range of 2-3MB, or less if possible.
  3. Use a CDN to deliver your initial bundle
3 Likes

Please run Bundle Visualizer and look for items taking up a lot of kilobytes.

One thing that might be useful for your app, is Meteor’s exact code splitting.

Another thing to check for, is whether you are loading entire npm packages, e.g.:

import {Facebook} from 'mdi-material-ui;

… when you may only need one or two functions from them, e.g.:

import Facebook from 'mdi-material-ui/Facebook';

Tree-shaking will take care of this automatically, and is on the Meteor roadmap. In the meantime, this is one of the things that can include your initial bundle size.

P.S. Your site is really cool!

4 Likes

Thanks guys. I am really looking forward for making it useful for the teacher community. Thanks very much for the quick responses.

I checked the bundle visualizer. The package called React-icons is taking up 8.77 MB. https://github.com/react-icons/react-icons/issues/154
I’m searching a solution for this issue

It sounds like you are importing all the icons from React-icons, rather than just the few that you need. Could you post the line of code that imports the icons?

2 Likes

I use named import which is only supposed to import the icon that I want. But unfortunately there is a bug within React-icons which imports the whole thing even in that way. Please see the github issur above.

It looks like the answer is in this reply from JaccoGoris in the issues thread:

It’s not really a bug – it’s a syntax issue I noted in my previous post here. :slight_smile:

1 Like

Thanks it helped me so much. I had ro remove react-icons and replace it with react-icons-kit The bundle size reduced to 5.8MB. Now much of the bundle size is contributed by semanti ui css and jsdom. Any of you know why JSDom is used and is it possible to get rid of that?

1 Like

if you hover over it in bundle-visualizer it will show you the dependency chain that caused it to be bundled

1 Like

One thing I can recommend for making the site feel like it’s loading faster is to have a loading screen. This needs to be injected by the server into the HTML boilerplate – you can’t put it in your client bundle because it won’t be loaded before your JS code.

I use the staringatlights:inject-data package. In my server code, I have this:

let injected = Assets.getText('initial-response.html');
Inject.rawBody('loader-body', injected);

Then in private/initial-response.html, I have something like this:

<div id="splashScreen">
  <div>
    <!-- I have an SVG logo here -->
    <div id="splashScreenLoadingDots" class="LoadingDots"><b></b><b></b><b></b></div>
  </div>
</div>

With this in my app’s styles (which are loaded before the scripts):

#splashScreen {
  position: absolute;
  top:0; left:0; bottom:0; right:0;
  background-color: #fafafa;
  z-index: 1000;
  overflow:hidden;
  display: flex;
  flex-direction: column;
  > div {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding-bottom:  10%;
    flex: 1 1 auto;
  }
}

.LoadingDots {
  /* CSS code for a spinner from https://loading.io/css/ or elsewhere */
}

Then, in a Meteor.startup() callback or whenever I know my app’s ready to display, I call $('#splashScreen').fadeOut().

You can see it in action at https://strummachine.com. The actual code is a fair bit more complex at this point; I also inject JS code to show an “updating” message when reloading with an update, an onerror hook to catch errors that happen during initial load, etc. You can see it if you View Source on that page and prettify the code; it’s not obfuscated since it’s pulled from an asset file, although I do a crude regex minification before injecting it. Oh, don’t use any ES6+ code in the injected file if you want to support older browsers, since it’s not Babel-ified.

5 Likes

That was a great and simple solution, thanks for sharing! The following changes really made our app load and feel faster:

  • added a splash screen to the index.html like you’ve shown to hide initial rendering and loading
  • used bundle-visualizer to find which packages I could lazy load to reduce bundle size by 40%
  • used Cloudfront as as CDN to load the app’s JS and CSS (this made a huge difference and reduced CPU load on the server as well, allowing us to handle more users per container)
  • used the staringatlights:inject-data package to take any data that was previously loaded on client during startup, and instead inject it directly into the index.html so that it’s available immediately without making a round trip method call to the server
2 Likes

Update for anyone seeing this in the future: I’m no longer using staringatlights:inject-data, although I can’t remember why I stopped using it, but @vikr00001 reported that it no longer works correctly for this use-case so that might have something to do with it.

For future reference, here’s the server-side code I use:

const head = Assets.getText('initial-response/injected-head.html')
  .replace(/<!--.+?-->/g, '')    // these three lines are crude minifiers which are optional
  .replace(/\s*\n+\s*/g, '\n')
  .replace(/\s\s+/g, ' ');

const body = Assets.getText('initial-response/injected-body.html');
const script = Assets.getText('initial-response/injected-script.html')
  .replace(/\/\/ .+$/g, '')     // also unnecessary optional minifiers
  .replace(/\s*([;)(}{:=])\s*/g, '$1');

WebAppInternals.registerBoilerplateDataCallback('initial-response', (request, data, arch) => {
  // if ( request.browser.name == "ie" ) {}    // in case you want to send something different to ol' IE...
  if (arch.startsWith('web.browser')) {
    data.dynamicHead = (data.dynamicHead || '') + head;
    data.dynamicBody = (data.dynamicBody || '') + (body + script).replace(/\n\s+/g, '');
  }
});

(WebAppInternals.registerBoilerplateDataCallback is what inject-data does under the hood.)

Then I have .html files in my private/initial-response directory. Note that injected-script.html includes the surrounding tag.

3 Likes