Very low meteor performance in production

Please give a complete practical guide.
How can we load each package and each component dynamically and lazily.

I see that your site has large images that can slow down, on the other hand does not use the rendering server, however it has great speed.
If possible, give examples of your code of how you loaded components and packages, and possibly photos, dynamically and lazily.

@jasongrishkoff
There is no doubt that you are using it in prerender, but we also use it and even activated its cache, but it still does not work well.
Maybe you use a very powerful server?

Hi @saeeed. I am not using pre-render here and nothing is cached or SSRed. The images are lazy-loaded using lazysizes and optimized based on your browser size, with different-sized assets server up as appropriate. Everything’s served via cloudfront, which does help with the speed.

Another “trick” I do is to only load the bottom half of the page when you begin scrolling. So it prioritizes the above-the-fold content and keeps some of the heavier stuff for later. There’s some SEO trade-off, but I’ve made sure to minimize that impact. Here’s a basic example of that.

import React,{lazy,Suspense} from 'react'
import Loader from './loader'
import TopHalf from './top-half'
const BottomHalf = lazy(() => import('./bottom-half'))
export default class LandingPage extends React.Component {

  state = { scrolled: window.scrollY > 150 }

  componentDidMount() {
    window.addEventListener('scroll',this.handleScroll)
  }

  handleScroll = () => {
    if (window.scrollY > 150) this.setState({scrolled:true})
  }

  render() {
    return (
       <TopHalf />
       {this.state.scrolled && <Suspense fallback={<Loader />}><BottomHalf /></Suspense>}
    )
  }

}
2 Likes

Impressive stats.

Curious to know how you managed to be properly crawled by google without prerender or SSR ? Without this Google generally is crawling blanks.

1 Like

Apologies, when I read the pre-render thing earlier I assumed it was in the context of loading the page quickly for the client. For SEO purposes I fall back to Galaxy’s built-in pre-rendering.

2 Likes

How do we know what pages are getting crawled by Google and the respective response? Can you guide pls

search your page in google, click on the little arrow next to the result and “check cached page”

Also set up proper google search console, google analytics, etc, etc…

1 Like

Use the Google Search Console

https://search.google.com/search-console/about

1 Like

My under development project scores 100% for desktop and 99% for mobile and I see absolutely no reason to score otherwise. There are plenty images on my home page, the code structure is massive now, with wall feeds, profile feeds, chat, social relations, notifications etc. I run the cheapest EC2 machine (nano) since I only have a couple test users and a free Atlas cluster. My Prerendering server is in DigitalOcean, cheapest machine for 5 USD/month.

What I do:

  1. My main render function is async
async function main () {
  const [
    { Meteor },
    React,
    ReactDOM,
    { createStore, applyMiddleware, compose },
    { Provider },
    { default: thunk },
    { BrowserRouter },
    { default: App },
    { createBrowserHistory },
    { IntlProvider }
  ] = await Promise.all([
    import('meteor/meteor'),
    import('react'),
    import('react-dom'),
    import('redux'),
    import('react-redux'),
    import('redux-thunk'),
    import('react-router-dom'),
    import('./layouts/App'),
    import('history'),
    import('react-intl')
  ])

// .....

ReactDOM.render(
      <Provider store={store}>
        <BrowserRouter history={history}>
          <App history={history} />
        </BrowserRouter>
      </Provider>, document.getElementById('app'))
  })
}

main()
  1. In App.js (the top component) I delay every useless library beyond the prerendering time.
    E.g.:
const handleGoogleAnalytics = () => {
    if (!window.GoogleAnalyticsObject) {
      import('react-ga') // I call this 6 seconds after my client startup, has no place in prerendering.
        .then(ReactGA => {
          ReactGA.initialize('UA-______-1')
          ReactGA.set({ page: window.location.pathname })
          ReactGA.pageview(window.location.pathname)
//.......
}

// I delay (timeout) the run of handleGoogleAnalytics() by 6 seconds.

I have libraries for notifications, video players, photo viewer etc … they all get downloaded (and loaded on view) from my CDN when required.
Example:

const loadHls = () => {
  if (!window.Hls && !requestedHLS) {
//    requestedHLS = true
    const script = document.createElement('script')
    script.src = 'https://_________/js/hls_v1.js'
    script.type = 'text/javascript'
    // script.integrity = integritySet.shaHLSJS
    // script.crossOrigin = 'anonymous'
    document.body.appendChild(script)
  }
}

// or

const loadPhotoSwipe = withCss => {
  if (!(window.PhotoSwipe || window.PhotoSwipeUI_Default)) {
    const root = 'https://__________/cdn/js'
    loadScript(`${root}/photoswipe-ui-default_v1.js`, null, 'shaPhotoSwipeUIJS')
      .then(() => {
        loadScript(`${root}/photoswipe_v1.js`, null, 'shaPhotoSwipeJS')
          .then(() => {
            if (withCss) {
              addCSS('https://_________/cdn/css/photoswipe.css', null, 'shaPhotoswipeCSS')
            }
          })
      })
  }
}

These are techniques for lazy, or deferred, or dynamic loading of code (JS, JS libraries or CSS).

On the routing side, dynamic loading of components:

/* globals localStorage */
import { Meteor } from 'meteor/meteor'
import React from 'react'
import { Switch, Route, Redirect } from 'react-router-dom'
import loadable from '@loadable/component'

const About = loadable(() => import('../../ui/pages/others/About'))
//...

const Main = (props, location) => {
 return (
          <Switch location={location.location}>
            <Route exact path='/about' component={About} />
          </Switch>
 )
}

export default Main

How do you know this is a dynamic load (which triggers the load of all its dependencies if they are not yet on the client):

In your Chrome network tab, when you navigate to a dynamically loaded page you will see the Name as “fetch” and the URL looks like this: https://www.activitree.com/__meteor__/dynamic-import/fetch

All these assets (code and libraries and styling) are for a page behind user login, there is no reason to expose to prerendering, scraping, crawling, and they are not present in my initial bundle. If you enter the website from a dynamically loaded page, you would load the bundle and then fetch the dynamic content which is still more feasible than processing a large bundle.:

I keep as many libraries as possible in my own Cloudfront CDN since this has far better regional availability than my static EC2 where I run Meteor. Still need to improve more on security (fetched code integrity).

@saeeed does this answer your query?

10 Likes

Great insights with good examples.

I see you don’t have any cached page on google yet (I guess you blocked the crawling so far). Do you know how google actually sees your page which would be useful in term of SEO ? As I mentioned to you in mp I had issues mixing loadable component and cloudfront served css which resulted in empty css file after I updated my site.

Using this tool (no idea if reliable):

it considered your website as empty more or less.
Do you use any prerendering service on top ?

<meta name="robots" content="noarchive">

This doesn’t affect indexing, position in searches etc. I could handle the “noarchive” via react-helmet for each and every page distinctively, but for the time being I am really not interested in this. If one of my users wants to go private, his public profile won’t be viewable in a google cache.

If you check Facebook you can see the home page keeps a cache:
Screen Shot 2021-02-22 at 18.50.34
However, user profiles are not cached:

1 Like

@paulishca could you please explain why you dynamically load Meteor and React/ReactDOM in main function? I saw your site, all these libs loaded via main bundle.

Another question is did you cache fetch requests?

THanks. I see. And you never had issue with your css on cloudfront and dynamic import ?

After I updated my site, link to former cached version of my css on cloudfront looked like this (while it was perfectly working while this build was up). As if it tried to cache the css again but the app was not there anymore for this id. As soon as I removed the dynamic import I didn’t get this issue anymore.

Probably linked to this code, but not sure why it happened

    if (request.url.query && request.url.query['meteor_css_resource']) {
      // In this case, we're requesting a CSS resource in the meteor-specific
      // way, but we don't have it.  Serve a static css file that indicates that
      // we didn't have it, so we can detect that and refresh.
      headers['Content-Type'] = 'text/css; charset=utf-8';
      res.writeHead(200, headers);
      res.write(".meteor-css-not-found-error { width: 0px;}");
      res.end();
      return undefined;
    }

Hi, this is the best practice: dynamic-import | Meteor API Docs. The last part of the documentation page refers to cacheing.

Since all my routes are “loadable” components I only async load libraries in the main function which yes, comes from the main bundle.

You can also check this article:
" You can dynamically load multiple modules at the same time via Promise.all()". The interest is for “at the same time.” (point 4.3).

https://2ality.com/2017/01/import-operator.html

1 Like

Try to keep track of your bundle file names as you update code bundles. Then visit those older CDN links. You should find your code there or error from Cloudfront in which case I would check the TTL for those older bundle files. You can set TTL to 100-300 days … or generally more than your re-prerendering time.

Thanks.

Do the dynamic import affect the generated css file too ? Like you have a dynamic import on your About Us component, it will be loaded directly if people arrive on the About us page but not if they arrive on the main page, would the css bundle generated be different in these cases ?

If you use LESS or SASS or import all your CSS in your main CSS file, they all go to the CSS bundle. I use a CSS library like Material or Bootstrap’, some override CSS for the main library but everything else is component imported CSS. It loads dynamically with the component.

1 Like

I used exactly your code in my Mine.

async function main () {
    const [
      React,
      ReactDOM ,
      { BrowserRouter },
      { HelmetProvider },
      Routes
    ] = await Promise.all([
      import('react'),
      import('react-dom'),
      import('react-router-dom'),
      import('react-helmet-async'),
      import('../../Routes/Routes'),
    ]);
  
    // const renderMethod = module.hot ? ReactDOM.render : ReactDOM.hydrate;

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

But I encounter an error :

This is why they say to not copy paste code. First understand it then write your own :wink:

5 Likes

@paulishca No, I really checked the code, there should be no problem.
Maybe it’s related to Router-History.Provider I don’t know what it means