web performance Web Performance Nginx, pagespeed and a simple node.js backend

Flying website

Basic setup — Where nginx and nodejs shares a mojito

In this first step, we will focus on having a local setup working, in docker containers, with a nginx+pagespeed container reverse proxy-ing requests to a universal NodeJS backend, a.k.a LeanJS.

If you wish, you can replace the later by anything in a container that can serve HTTP requests.

Our target architecture (in green, the parts we focus on in this article):

target architecture, nginx and the backend highlighted

Backend setup

As an example backend, we will use a little (node) starter project I wrote a while ago to learn React, called LeanJS. It is pre-packaged as a container image and it will serve us well for the demo purpose. You can, of course, use anything else that serves HTTP.

docker run --name backend -p 3080:3080 -d rdorgueil/leanjs

After a little while (the first run needs to download the rdorgueil/leanjs image), you should have a backend running as a background container, creatively named backend.

Open a browser and check that something answers on localhost:3080.

Welcome, beautiful universal LeanJS default homepage!

Good? Great.

Nginx / Pagespeed setup

One of the cons of ngx_pagespeed is that it requires to compile your own nginx with it, and probably you will get your systems dirty doing so.

The good news is I already wrote a little recipe to compile ngx_pagespeed along with nginx in a Debian package, based on the official nginx's Debian releases.

You can use the docker image factory to rebuild it for yourself, or just use the image from docker hub.

Note

Schematically, the okdocker/nginx recipe does the following:

  • create a build container
  • install the build dependencies
  • download the source package
  • adds ngx_pagespeed in the source
  • changes the .deb manifest/metadata files
  • compile the shit
  • create a clean runtime container
  • install the newly built Debian package

To use this image to serve an actual backend, you'll need to configure nginx. I usually start with the okdocker fork of H5BP's nginx configuration boilerplate that should cover pretty much all your basic needs.

Let's build and run a container based on this okdocker/nginx image:

git clone https://github.com/okdocker/server-configs-nginx.git nginx-container
cd nginx-container
make release
make run

That should be it, a nginx web server is now running on your docker engine's host (most probably locahost), listening to the :80 port, with a rather unimpressive page served.

Welcome, stupid default hello world page!

Yet, you can look at the source and notice the difference (removed whitespaces and some obscure javascript used for instrumentation, mostly) with the original in the nginx config repository:

Woot! Where are those whitespaces?

Plugging our backend — Where nginx is teinted with magic

You should still have the LeanJS backend running in a container, that should be named backend. If you don't, go back to the backend setup step and start over.

Let's change the default host to proxy our backend. Open sites-enabled/000_default (or sites-available/default, which is the former's symlink target) and add change it so it looks like this:

server {
  listen [::]:80 default_server deferred;
  listen 80 deferred;

  # Path for static files
  root /var/www/default;

  # Specify a charset
  charset utf-8;

  # Custom 404 page
  error_page 404 /404.html;

  # Pagespeed basic config
  include pagespeed/basic.conf;
  include pagespeed/aggressive.conf;
  include pagespeed/admin.conf;

  # Pass requests to the internal backend proxy location.
  location / {
    try_files $uri @backend;
  }

  # Internal backend proxy location.
  location @backend {
    internal;

    if ($request_filename ~* \.(jpg|jpeg|gif|png|bmp|ico|pdf|flv|swf|txt|css|js|otf|eot|svg|ttf|woff|woff2|map)$) {
      expires 7d;
      add_header Cache-control public;
      access_log off;
    }

    include proxy/basic.conf;
    proxy_pass http://backend:3080;
    proxy_pass_header Cache-Control;
  }
}

Build and run again the nginx container, with a docker link to the backend:

cd nginx-container
DOCKER_RUN_OPTIONS="--link backend:backend" make release run

Open again localhost:80 in your favorite web browser and stare for a few seconds at your backend's output.

Now, nginx is serving our backend.

Once again, you can have a look at the source and see whitespaces removed, instrumentation added, and assets rewritten (you may notice they're not on the first load, it is because pagespeed will progressively enhance the output over time, learning from what users actually request).

And the wizard-esque source...

Amazing right? Isn't that octarine-esque?

« Previous

Next in the serie: Amazon Cloudfront as our Content Delivery Network »




Caveats

  • Cache headers are relying less on browser, and more on nginx, which can lead to problems with resources you want to cache client side (I had problems with Edge Side Includes, for example).

    pagespeed ModifyCachingHeaders off;
    

    Beware though as this setting can have pretty unexpected side-effects on the pagespeed behavior. Try to avoid it if you can, and be conscious that you can cause yourself headaches with this setting.

  • Some pagespeed filters are riskier than others and can break your website, depending what you're already doing. You probably won't have any problems for small and very static websites, but if you have a lot of JavaScript magic, you can encounter a few problems. If you start to experience glitches, try disabling all the filters, see if it solves the problem, and if it does, reactivate the filters one by one (or bisect the activated filters to find the culprit, to debug it in log(n) passes instead of n).

    For example, when getting this article ready, the remove_comments filter broke the React app. I first confirmed that pagespeed was the culprit by adding a pagespeed off; directive in the config, then bisected the active filters (by disabling half, re-enabling half of the half, etc.) to find the dubious filter. By following this process, you should not take more than a few minutes to debug pagespeed caused issues.

Share the love!

Liked this article? Please consider sharing it on your favorite network, it really helps me a lot!

You can also add your valuable insights by commenting below.