You've just read How to lose Weight in the Browser and you want to know to slim down your Mojo app. Part of that process is preventing the browser from requesting files that hardly change. I spent a well-caffeinated afternoon trying to do that with Mojolicious. I've been 'round the houses, and spoiler alert I didn't find the answer until the very end, kind of like your favourite Christmas animated special with a small woodland creature narrating "The Gruffalo's HTTP header".

A Children's Story

Our beloved small woodland creature needed to display a web calendar with forest events pulled from a database. Perl could get the event data and package it as a JSON feed. Mojolicious could prepare the webpages with the correct JSON feed for each user. With some JavaScript libraries to display the web calendar, all would be well in the forest.

Everything except the JavaScript libraries are lightweight. And everyone knows a page reload goes so much faster if it doesn't have to download the JavaScript every time. Those libraries won't change for months! If only the client browser knew that it could use the file that it had downloaded last time.

The secret, of course, is to set the Cache-Control field of the HTTP header, but how?

First, there was a Horse ...

Everybody using Apache would be thinking about using mod_expires which looks quite easy, except that Apache wasn't being used to serve the webpages.

... but the Horse mentioned where there were some sweet Cache-Control directives to munch on and while continuing to graze on some HTTP Caching pages that had been downloaded earlier. The small creature moves on.

... and then there was a Toad

The forest creatures used the Hypnotoad web server that comes with Mojolicious to serve their pages. They found it a good fit for their arboreal production environment.

It can set the HTTP headers to turn it into a reverse proxy, but a popular setup is sitting Hypnotoad behind Nginx or Apache/mod_proxy. Those servers should let you play with the Expires header. But the Toad didn't quite have what this particular rodent was looking for.

An aside - No, I didn't mention Plack. Maybe if I'm good this year, Santa will tell me how I should be using it. Probably something to do with

Plack::Response->header('Expires' => 'Tue, 25 Dec 2018 07:28:00 GMT');

but I wouldn't know and neither did our narrator.

... and then there was a Unicorn

Well, that was easy. Just use the standard Mojo::Headers module to set the Expires header.

But, wait! That sets it for a page which isn't that big at all. Our furry friend only wants to stop JavaScript files from reloading every single time which were killing the Sciuridae mobile experience. Hmmmm.

... and then there was a Rhino

JavaScript rhino image

If the JavaScript lives in the <head> tag, then the page body won't be parsed until the script is downloaded. Some people get around this by putting the script just before the end </body> tag, but the JS still has to download. There are a couple of attributes that are hiding in the bushes. Async and defer tell the browser to continue parsing the HTML while the JavaScript is downloading. These two wise owls can help decide which one to use.

Tell your script to load after the main page with this tag helper in your template

%= javascript '/js/lib/jquery.min.js', defer => undef

which produces

<script defer src="/path/to/script.js"></script>

If you want it in the <head>, you'll have to put it in your layout otherwise the template will do.

... and finally, there was a Man in a Hat

Luc Didry gravatar

"But the JavaScript needs to load FIRST!", squeaked the nut-botherer.

Sigh - this really is one demanding Sciurus vulgaris.

Well ... it is Christmas, but you'll need to install the StaticCache plugin written for you only last year by Luc Didry. It sets the Control-Cache header for all static files served by Mojolicious. With the nut.js and nut.css files in the public directory (properly minified of course), they should only be downloaded once and use the cached version until it expires. The default max-age is 30 days and if you want you can even cache during development with even_in_dev => 1.

The magpies in the forest had cluttered the calendar with 3 JavaScript libraries, 3 CSS files and 4 logos. Sure, the biggest and shiniest was only 66 kB and the whole collection was a paltry 164 kB, but bandwidth is precious in the wilderness. Before using the StaticCache plugin, the calendar rated a 92 on Google's PageSpeed Insights.

With the StaticCache plugin loaded

sub startup {
    my $self = shift;

    $self->plugin('StaticCache' => { even_in_dev => 1 });
    ...
}

page speeds are now 93 !!!! WOW! It's one faster! said Nutgel Tufty-tail and everyone in the forest cheered.

And that, dear readers, is how the squirrel learned to store nuts for the winter ... in a cache. Good night, little kids, good niiiight.

Try it out for yourself

The Browser calories plugin for Firefox, Chrome and Opera breaks down the file sizes of your web page into a nice little traffic light report measuring the HTML, images, CSS, JavaScript and other parts of your page against user-configurable limits on what you think is acceptable.

Google's PageSpeed Insights measures performance on both mobile and desktop.

Hopefully, the increasingly awkward attempt at writing in a narrative style didn't get in the way of a new idea or two. Let me know if I've missed something.

Image by Peter G Trimming CC BY 2.0

Tagged in : advent, caching, headers

author image
Boyd Duffee

Boyd Duffee has been hanging around the edges of the Perl ecosystem for many moons, picking up new bits of shiny to make SysAdmining more interesting. He's interested in Data Science, Complex Networks and walks in the woods.