Category: html

Static website hosting in 2019

TL;DR: use Gitlab+Netlify.

With the push for back to static, I found myself looking for static hosting again. And with some specific requirements:

  • DNS on Cloudflare
  • SSL support for 4th level subdomains (sub.sub.domain.com). Which means SSL termination can’t happen on Cloudflare – their free plan only allows to proxy 3rd level subdomains, and business one is too expensive
  • Not necessarily free, but unexpensive, at least.

And here’s what I came up with:

  1. First idea: AWS S3. Looks good at first site, but the problem is that it doesn’t support SSL without Cloudfront. So you have to create a CF distribution for each site, then invalidate it each time it’s updated… too complicated for a simple static website.
  2. Second: Github pages. Very good, except that it doesn’t allow to publish sites from private repos, unless you pay for your account. Pass.
  3. Gitlab pages, on the other hand, do allow that. Sadly, they have no automatic SSL renewal, they actually recommend to “When you finish setting up, just put in your calendar to remember to renew the certificate in time“. Seems ridiculous for a service that is all about DevOps and IaC.
  4. And finally I found the holy grail: Netlify. It has everything that’s on the list, plus automatic Jekyll (and other site generators) support. And their own CDN. So all you have to do to deploy is push to website repo.

So for now, Netlify is my static hosting platform of choice. Still, there are some gotchas and things where it can be improved:

  1. It’s a lot of clicking to create a website: create, pick repo, receive Netlify’s default subdomain, add a CNAME to DNS, enable SSL in settings, add redirect from default subdomain to the main domain in netlify.toml. All this is manual. That is fine while you have one or two sites, but when it’s a dozen, you really need to employ infrastructure as code. An Ansible role where you could just specify a list of domains and corresponding repos to have them all created automatically would be perfect.
  2. Netlify gets full access to your Gitlab (Github, etc) account. In some cases, that might be acceptable. In others, you might want to create a dedicated account for it instead and grant access only to the website repos.
  3. Make no mistake – if you’re not paying for the product, you are the product. (Which is why S3 was my first go-to option: I’d rather pay a little, than nothing at all. Alas, S3 just didn’t cut it.)

Navbar menu for a large static website

Say you need to make a navigation menu bar for a large static (HTML) website. Very large, maybe thousands of pages.

First problem would be – how to put this menu onto every page. Remember, the site is static, files can’t be changed. Fortunately, even static pages can be modified with a combination of SSI (server side includes) and Nginx sub_filter directive. So you can generate a separate HTML file for the menu (SmartMenus is a good choice for that) and then insert it at web server level. Nginx example:

ssi on;
sub_filter '</body>' '<!--# include file="/navbar.html" --> </body>';
sub_filter_once on;
sub_filter_last_modified off;

Where navbar.html is SmartMenus-style list. Very clean and pluggable solution. Can be styled, disabled/enabled when necessary, etc.

It will work fine more for small/medium-sized sites, maybe hundreds of pages. However, once we’re talking thousands, you’ll probably notice that menu becomes bigger than the pages themselves. It could be a a few Mb. And once it gets inserted into every 50 Kb page, you’ll notice that every page load is painfully slow – no surprise here. And even after reload, the pages will be clunky – having to parse several Mb of HTML with Javascript is expensive.

An obvious workaround to that would be to load the menu once and cache it. But it’s not possible if it gets inserted into every page directly.

Well… it sounds like a job for iframe, right? Put one on top, and display the menu there. Of course, wrong. I don’t want to bore you with details, let’s just say that iframes were ugly (from aestetic and programming perspectives) in the 90s, and they still are. Just avoid them.

It’s hard to think of an alternative, though. But the trick is that we don’t really need the full menu all the time. What we need is appearance of having a full menu all the time, and actual functionality ony when it’s being used.

With this in mind, we can try to chain menu load:

  1. Create a small menu with only top-level items, put it into a <div>
    <--! navbar_top.html>
    
    <div id="sm-menu-wrapper">
      <div class="sm_navbar">
        <ul class="sm sm-blue" id="sm-main-menu">
          (Normal SmartMenus HTML list)
        </ul>
      </div>
    </div>
  2. Insert it into every page with SSI.
    sub_filter '</body>' '<!--# include file="/navbar_top.html" --> </body>';
  3. On page load, run a small Javascript which replaces this <div> with contents of the file which contains full menu.
    <!-- SmartMenus jQuery init -->
    $(function() {
        $('#sm-menu-wrapper').load('/navbar.html', function() {
          $('#sm-main-menu').smartmenus({
            subMenusSubOffsetX: 4,
            subMenusSubOffsetY: -5
          });
        });
    });
  4. Set max expires time for the full menu file.

Now, actual pages are not bloated and will load fast, and after load, menu will be changed to full version from the cached file.

It’s almost perfect, but (in SmartMenus case) not quite. The reason is that items that don’t have submenus look different from those that don’t. Because of that, top bar flickers on page load, which is distracting. To avoid that, make sure that short menu version looks exactly the same as the full one when unexpanded. For example for SmartMenus, you’ll have to add class=”sub-arrow”> to menu items with (future) children.

And that’s about it. Without changing a single file, we added huge responsive AJAX navigation menu bar to every page, without any real overhead on server or user machine.

Some parts were left out, for example SmartMenus also needs some JS and CSS inserted, but that’s trivial with power of SSI and sub_filter. To see an example of the solution in action, check this link.