Simple guide for optimising your Meteor app with Cloudflare (Cache, TTFB, Firewall, etc)

At EducationLink we use Cloudflare for many reasons, the main two are: a little but more secure (due to WAF) and much faster.

We could probably use Cloudfront as our CDN, but we think for now it’s much easier to offload to Cloudflare so we don’t have to deal with any code or integration.

Here it is our small and simple guide to setup Cloudflare to work perfectly with you Meteor app.

Cloudflare settings:

Speed:

Auto minify: ALL ON
Polish: Lossy
Mirage: ON
Rocket Loader: Automatic

Caching:

Caching level: Everything
Browser cache expiration: 7 days
Always online: ON

Crypto:

SSL: Flexible (for our website, but we overwrite in the Page rule)
Opportunistic encrypt: ON
TLS 1.3: ON
Automatic HTTPS Rewrites: ON

Traffic:

Argo: ON (Paid)

Network:

HTTP/2 + SPDY: ON
Websockets: ON

If you just use this settings your app will enter in a infinity loop of reloading after a new deploy. Because of the Caching level as “Everything” the new HTML file Meteor creates will never be downloaded to the client, because Cloudflare will always serve from the cache.

A solution for that would be to create a Page Rule or set the Caching level to Standard. The thing is, when on Standard, HTML files will not be cached. In our case we wanted to serve the initial file much faster than what our app was doing.

We managed to do that with the script below (improvements from ~800ms or more to 20ms to download the HTML file).

Install the Cloudflare NPM package:

meteor npm install cloudflare
import { Meteor } from 'meteor/meteor';
import Cloudflare from 'cloudflare';

// (On the server) Clear Cloudflare cache everytime Meteor app starts
Meteor.startup(() => {
  const settings = Meteor.settings.private;

  if (settings.env !== 'PROD') {
    return;
  }

  const cf = new Cloudflare({
    email: settings.cloudflare.email,
    key: settings.cloudflare.key,
  });

  cf.zones.purgeCache(settings.cloudflare.zoneId, {
    purge_everything: true,
  }).catch(error => console.error(error));
});

Now, every time you deploy a new version (or the container restarts, or every time you spin up another one) It will clear the cache on Cloudflare.

Cons of this approach: If you have auto-balancing (Galaxy does not have it) this may clear the cache more than you would like to. Furthermore, if you have your website in the same domain as your app, it will loose its cache too.

It would be possible to do something more advanced but this is a good start for most people interested in using Cloudflare.

With this setup, package files, CSS, images, etc will be served from the fast network of Cloudflare, together with the HTML with no extra code. Now that the code splitting will load files from HTTP, they will also be cached, freeing up your sever from further load and increasing response times of your app.

9 Likes

That’s not quite true. Since it’s a POST request it doesn’t get cached. Don’t know, if that was different in a previous version.

@raphaelarias

are you still running the same configuration?

I followed your guide and also purge files (by file, not everything). But I still end up on infinity reloading.
So I wonder how this is working for you.

On manual reload the browser gets the html from Cloudflare, that’s fine. But on reload triggered by Meteor it gets the document from the browser cache since Cloudflare adds some Browser Cache TTL / Expiration time which the browser respects in that case.

Update:
I changed settings for “Browser Cache Expiration” to “Respect Existing Headers” and added cache headers for static files in my application itself. That’s working fine so far.

WebApp.rawConnectHandlers.use('/static', (req, res, next) => {
  res.setHeader('Cache-Control', `public, max-age=${MAX_AGE_IN_SECONDS_IMAGES}`)
  res.setHeader('Expires', new Date(Date.now() + MAX_AGE_IN_SECONDS_IMAGES * 1000).toUTCString())
  next()
})
2 Likes