Very low meteor performance in production

Hi
I finally uploaded my software to the server, but when I check the performance of the site with Google tools and https://gtmetrix.com/, we see that it is very low.

-My server has 2 G RAM and 2 CPU cores.

  • I used prerender and handled it with my nginx middleware.
  • I also activated gzip and put it in number 8.
  • I also used my lazy loading React.
  • And I compressed all the icons and logos .

**Server address : ** http://194.5.195.113/

Thank you for taking a look at it, any suggestions and experiences are welcome
But I do not know exactly where the problem comes from.

from gtmertix :flushed:

in google pages speed insights :flushed:

image

My LH show me this for Mobile:

Did you turn off any chrome extensions like react dev tools, meteor dev tools?

Anyway, you can try to lazy load some meteor packages, but in most cases the package should be cloned into /packages folder and manually fixed.

I had an experiment with lazy:

  1. after tuning some meteor packages with {lazy:true}, with commented out all app code
  2. without tuning, with commented out all app code
  3. our application “as is”. need refactoring)

All app code should be imported via dynamic imports.

2 Likes

I guess the meteor codes themselves will be split. Like lazy in React.
Is that so?
Thanks for your guidance.

@afrokick I do not know exactly what Dynamic import does and what its purpose is, thank you for your explanation.

It’s true only for packages with api.mainModule(...,{lazy:true}). Maybe api.export set lazy under the hood, but IDK.

1 Like

@afrokick How to implement Dynamic import and lazy.
By example please
Thankful

This PR contains changes to replace not lazy to lazy Lazy module export by CaptainN · Pull Request #395 · Meteor-Community-Packages/meteor-collection2 · GitHub

@captainn maybe you have success case for such optimisation?

I just cloned package/collection2 folder to my_project/packages/ folder (or you can you symlink) and make changes from PR.

In your client’s startup script, you should import it dynamically like:

imports/startup/client/index.js:

Meteor.startup(async () => {
  await import("meteor/aldeed:collection2");//dynamic import for meteor package

  await import("yourRoutesFile.js"); // your entry point for router
  await import("..."); //other stuff if it needs
});
2 Likes

You have to be aware, that some packages immediately add a lot of code to your bundle via api.addFiles. This can increase your bundle dramatically. Besides that here are some (incomplete) tips for reducing your bundle size:

  • use dynamic import where you can, especially on a router level: only the code relevant for the current route should be imported.
  • import layout-related components first, then load the data after the component has at least drawn once and display some loading indicators while the data loads
  • avoid packages that add many files to the bundle via api.addFiles
  • use api.mainModule on your own packages and combine it with dynamic import to load package content not before it is actually used
  • defer loading of css styles and fonts after start and provide a minimal css for the landingpage / loginpage etc.
  • load all images, videos and iframe content lazy (I use lozad NPM package for this but there are many others for this)
  • don’t forget to dynamically import all your JSON content! It is easy to hard-wire JSON files into your bundle. Especially large files for i18n increase bundle size a lot.
  • use a service-worker for caching
  • avoid auto-publishing where you can and start subscribing after your initial bundle has arrived at the client (use Meteor.startup to defer execution after bundle loading)
  • set the html head <meta charset="utf-8"> at the very first tag in your head or the browser will re-evaluate all your html once it finds this tag anywhere else
  • use google lighthouse (in chrome embedded) to find yet hidden issues

These are rather general hints and should apply independently from using server/client or SSR.

6 Likes

My comments will not be agreed by all.

Some of the parameters/ scores make relevance(or need to be prioritised ) depending on your app and its purpose. If you are using your meteor app site as a means of customer conversion or stickiness then yes quick loading is important otherwise they may loose interest.

In other words take a call based on your end goal. This does not mean performance is not important, it is upto you to decide which parameter your end user looks for and focus on it.

I found the link responsive and it seems fine. But then performance depends on so many other things and they change over a period of time.

Meteor Framework itself brings its constraint so focus on solving that is possible to be solved. And not all things have to be done on day one.

Also, note gtmetrix server is based out of Canada, not sure where your server is …it will add to latency.

3 Likes
  1. Use the bundle-visualizer package to find what is affecting how big your client bundle size is, which slows download speed.
  2. Use the chrome devtools profiler to see what is taking up time and CPU cycles while the app is rendering, it might just be taking awhile to render.
  3. Use MontiAPM to help analyse the response times of your server methods and subscriptions. It seems like waiting for data may be why it’s slow.
  4. Make sure any Mongo queries have proper indexes set up on the collections so your queries aren’t slow.
  5. Use Meteor.defer() and this.unblock() where possible in your methods to increase throughput.
2 Likes

HI @jkuester , Is this applicable to Blaze also?

@mixmatric yes, totally! I use Blaze and apply all of these techniques.

@afrokick
I did not understand the import dynamics exactly, if you can, follow the example below. thanks again .

import React from 'react';
import {render} from 'react-dom';
import {BrowserRouter as Router} from "react-router-dom";
import {HelmetProvider} from 'react-helmet-async';
import Routes from "../../Routes/Routes";
const helmetContext = {}
render(
    <HelmetProvider context={helmetContext}>
        <Router>
            <Routes/>
        </Router>
    </HelmetProvider>
    ,
    document.getElementById('App')
);


I’ve been working on dynamic import for the past couple of days, and this is an example of what I’ve just done,

A perfect example of dynamic import

Dynamic import of the specified route, For example: / user/center

const Routes = (props) => {
    const staticRouter = [
        <Route exact path="/" component={Home} key="s1"/>,
    ];

    const [loading, setLoading] = useState(false);
    const [routes, setRoutes] = useState(staticRouter);
    const account = props.location.pathname === '/user/center';

    const dynamicRouter = async () => {
        setLoading(true);
        if (account) {
            const dynamic = await import('./event/dynamic');
            const resultRouter = dynamic.Generate(props);
            setRoutes([...staticRouter, ...resultRouter]);
        } else {
            setRoutes([...staticRouter]);
        }
        setLoading(false);
    }

    useEffect(async () => {
        await dynamicRouter();
    }, [account]);

    if (loading) {
        return <Loading/>
    }

    return (
        <Layout>
            <Switch>
                {routes}
                <Route component={NotFound} key='n1'/>
            </Switch>
        </Layout>
    )

Dynamically import the contents of the routing file
./event/dynamic

import React from 'react';
import { Route } from 'react-router-dom';
import UCenter from '../../pages/UCenter';

export const Generate = props => {
    return [
        <Route path="/user/center" component={UCenter} {...props} key="d1"/>,
    ];
};

Refer to this post,go

See this post on how to tune meteor, go

Using bundle-visualizer I just managed to find a serious problem with react-icon, which made the packaged file shrink by 40% and reduced its speed by 1 second for the first opening and 600 milliseconds for the second opening

1 Like

However, I also have a problem to solve, using the above code, SSR function is invalid, SSR is not supported, according to the @nathanschwarz prompt, do not use react on the server side, but I do not understand, has not been successful

Here’s how he did it, go

1 Like

First of all, you should run bundle visualizer meteor --extra-packages bundle-visualizer --production and go to http://localhost:3000 to understand what exactly should be extracted from main js bundle.

Probably your ../../Routes/Routes file contains many imports, which can be dynamically loaded.

The second improvement which we’r now implementing - cut off React+React-dom from the main bundle and load it after DOM ready.

Lets see example, based on your code.

I move your code from project_name/client/index.jsx(maybe another file, IDK) to project_name/client/react.jsx;

project_name/client/react.jsx:

import React from 'react';
import {render} from 'react-dom';
import {BrowserRouter as Router} from "react-router-dom";
import {HelmetProvider} from 'react-helmet-async';
import Routes from "../../Routes/Routes";

const helmetContext = {};

render(
    <HelmetProvider context={helmetContext}>
        <Router>
            <Routes/>
        </Router>
    </HelmetProvider>
    ,
    document.getElementById('App')
);

Rename index.jsx → index.js and add a new code:

import { Meteor } from 'meteor/meteor';

// Custom splash screen/loading indicator
// You can use css animation, more complex HTML etc.
const splashScreen = document.createElement('div');
splashScreen = `Loading...`;

const showSplashScreen = () => {
  document.body.appendChild(splashScreen);
}

const hideSplashScreen = () => {
  document.body.removeChild(splashScreen);
}

Meteor.startup(async () => {
  showSplashScreen(); //also we can call it in DOMContentLoaded event

  await import('./react.jsx'); //import our react's stuff dynamically

  hideSplashScreen(); //hide loading indicator
});

react-dom should be removed from the main bundle after these changes. If not - it means that some meteor package uses it and should be lazy loaded, as I described above.

To understand my approach, I suggest to create an empty project and try to improve the bundle size.

For our project, we expect that bundle size should be 400-500kb after such optimisation, and our routes file with react-dom - another 200-300kb.

2 Likes

I followed exactly this example, but it did not change.
I checked the packages, packages like react-bootstrap and ostrio: files are relatively large.
Now exactly how can I implement them with lazy?
As I understand it, I do not have to do anything special, but that package must take the necessary measures.
Thank you for explaining on a package with an example

@saeeed you should import() it dynamically, for example with FlowRouter using .waitOn() method, like in our example repo loading all necessary files dynamically, and upon routing/navigation event, and even conditionally dynamic 404

2 Likes