SSI

If you are trying to avoid complex server-side scripting languages and JavaScript, SSI might be a good option for you. It's a very simple server-side scripting language that has very limited control flow.

It's important to note that you can't perform loops. This should be solved using a workaround, by making smart use if #if's and #include's. Even though it is possible I'd advise questioning if SSI is still the appropriate environment to be doing the operation in.

You can find it's directives listed on the Nginx SSI-module documentation page.

Fun benefit: By replacing your JavaScript with SSI and or CSS, your website might just speed up and your site should become more browser-proof. (Read: Your website's functionality will work in more browsers)

An example

In this section, we'll create a site using bootstrap, SSI and some simple binaries. It should be fast, simple, feel like a modern-day web application and... lack JavaScript whenever possible.

I like learning by doing. In line with that sentiment, I'm aiming to make this example as stupid as possible. Just to get you started and have a point of reference.

You can follow along with the steps in this example by opening the files in our git repository at GitLab. Every commit has been named after a step and contains the files you need to tag along.

If you want to get a list of all the relevant commits, you can use:


git log --grep 'Step' --pretty=oneline
                    

Step 1: Manual maintenance

Our bootstrap site looks great! It has a menubar with 3 fields and some content. Problem is there's shared content across all our pages (even the posts). If we need to edit one of those sections, we'd be forced to edit it for each page.

For those of you who are proficient at HTML, you'll notice some error crept into our site: Shared div's where forgotten to be updated and active menu-items were forgotten to be set to current.

Let's fix.

Step 2: Sharing sections

Let's start by sharing our common headers throughout our pages. By moving the following lines into a new file, we can include them in our pages later.

File: ssi/shared-head.shtml

<title>Bootstrap + SSI Example</title>

<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">

<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
                

We can refer to the above file by using the following rule:


<!--# include file="/ssi/shared-head.shtml" -->
                

Nearly there! Now we just need to ask Nginx to enable SSI:


server {
    listen       80;
    server_name  localhost;
    ssi          on;

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}
                

The only rule you need to add to enable SSI is:


ssi on;
                

It's that simple!

But including portions of code is only useful if we can also enforce some conditions to its contents. Let's say the navigation bar for example: Using what we have learned up to now, we would only be able to include a static menu bar. But we want our current page to be distinguishable from the other menu options.

Introducing: variables.

Our navigation bar currently looks like this:


<nav class="navbar navbar-expand-sm bg-dark navbar-dark">
    <ul class="navbar-nav">
        <li class="nav-item active">
            <a class="nav-link" href="/">Home</a>
        </li>
        <li class="nav-item">
            <a class="nav-link" href="/second">Second page</a>
        </li>
        <li class="nav-item">
            <a class="nav-link" href="/posts">Posts</a>
        </li>
    </ul>
</nav>
                

Looking closely, the only noteworthy difference is:


<li class="nav-item active">
                

Let's try to make it a bit more dynamic!

We don't even need to use any control flow to mark the correct page in the navigation bar. Let's build the following file:

File: ssi/navigation.shtml


<nav class="navbar navbar-expand-sm bg-dark navbar-dark">
    <ul class="navbar-nav">
        <li class="nav-item <!--# echo var="HOME" -->">
            <a class="nav-link" href="/">Home</a>
        </li>
        <li class="nav-item <!--# echo var="SECOND" -->">
            <a class="nav-link" href="/second">Second page</a>
        </li>
        <li class="nav-item <!--# echo var="POSTS" -->">
            <a class="nav-link" href="/posts">Posts</a>
        </li>
    </ul>
</nav>
                

We've replaced all occurrences of a page with some kind of placeholder:


<!--# echo var="HOME" -->
                

When Nginx is handling a request, and it reaches such a block, it will echo the given variable's value into its position.

We can now invoke the navbar like so:


<!--# set var="HOME" value="active" -->
<!--# include virtual="/ssi/navigation.shtml" -->
                

Or on another page:


<!--# set var="SECOND" value="active" -->
<!--# include virtual="/ssi/navigation.shtml" -->
                

I just love how simple this is. We now have a site with shared content which each can be controlled from a single source. The copy-paste errors we had earlier are gone as well! Great stuff.

Step 3: Flow Control

But wait! There's an issue... When we browse through our site and inspect an un-active navigation item:


[...]
<li class="nav-item (none)">
[...]
                

Areugh. That's disgusting.

This is due to our Nginx trying to request the value using SSI, but not coming up with one. It results in a string: (none). Let's fix that and call it a day.

We can add a condition evaluating the contents of our variable before we fall into decay:


diff --git a/ssi/navigation.shtml b/ssi/navigation.shtml
index 6d67ac5..e12ecd1 100644
--- a/ssi/navigation.shtml
+++ b/ssi/navigation.shtml
@@ -1,12 +1,12 @@
         <nav class="navbar navbar-expand-sm bg-dark navbar-dark">
             <ul class="navbar-nav">
-                <li class="nav-item <!--# echo var="HOME" -->">
+                <li class="nav-item<!--# if expr="$HOME" --> <!--# echo var="HOME" --><!--# endif -->">
                     <a class="nav-link" href="/">Home</a>
                 </li>
-                <li class="nav-item <!--# echo var="SECOND" -->">
+                <li class="nav-item<!--# if expr="$SECOND" --> <!--# echo var="SECOND" --><!--# endif -->">
                     <a class="nav-link" href="/second">Second page</a>
                 </li>
-                <li class="nav-item <!--# echo var="POSTS" -->">
+                <li class="nav-item<!--# if expr="$POSTS" --> <!--# echo var="POSTS" --><!--# endif -->">
                     <a class="nav-link" href="/posts">Posts</a>
                 </li>
             </ul>
                

Ha! Finally. Sweet sweet consistency.

Step 4: Topping it off

We are at a point where we've made our site a lot more maintainable, and we are not using any weird JavaScript or any complex scripting language.

If you'd be using this as the base for a project, at some point during the development you'll want to include content from a database, or rather an API. Good thing is: It's highly likely you can still make that happen with just SSI!

Just like the includes we did earlier, you can query information from external applications using the include statement. Just pop it in, consume the information and thrive in a world without JavaScript.


Thanks for reading, happy hacking!