Navbar menu for a large static website

Say you need to make a navigation menu bar for a large static (HTML) website. Not the Jekyll/Hugo kind - those are easy to manage. I’m talking about something from the past, a legacy monster.

First problem would be - how to put this menu onto every page? Remember, the site is static, files can’t be changed (unless you want to comb through thousands of files). 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 a 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 might 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 only 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.

Comments