Liquid navigation tree generator
Put a lot of work into refactoring this code, trying to maximize for readability and simplicity in an effort to increase maintainability. I'm posting my results here since I know creating recursive, dynamic, no-plugin navigation is an issue Jekyll users often face, and some are likely to land here searching for a solution.

nav.html

{% comment %} Init call with base_url (optional) and max_depth. {% endcomment %}

{% if lvl_base_url == nil %}
  {% assign lvl_base_url = '/' %}
  {% if include.base_url %}
    {% assign lvl_base_url = include.base_url %}
  {% endif %}
{% endif %}

{% assign current_lvl_url_length = lvl_base_url | split: '/' | size %}
{% if lvl_base_url == '/' %}
  {% assign current_lvl_url_length = 1 %}
{% endif %}

{% capture links %}
  {% for node in site.pages %}
    {% assign node_url_length = node.url | split: '/' | size | minus: 1 %}
    {% assign filename = node.url | split: '/' | last %}
    {% if node_url_length == current_lvl_url_length and node.url contains lvl_base_url and filename != 'index.html' and filename contains 'html' %}
      &lt;li class="page-link"&gt;
        &lt;a href='{{ node.url }}'&gt;{{ node.title }}&lt;/a&gt;
        {% if include.max_depth &gt; current_lvl_url_length | plus: 1 %}
          {% assign lvl_base_url = node.url | replace: '.html' | append: '/' %}

          {% comment %} Include can access parent state; hence no variables passed explicitly: {% endcomment %}
          {% include nav.html %}

          {% comment %} Reverse state changes made by child: {% endcomment %}
          {% assign current_lvl_url_length = current_lvl_url_length | minus: 1 %}
          {% assign directory_to_remove = lvl_base_url | split: '/' | last | append: '/' %}
          {% assign lvl_base_url = lvl_base_url | replace: directory_to_remove %}
        {% endif %}
      &lt;/li&gt;
    {% endif %}
  {% endfor %}
{% endcapture %}

{% assign links = links | strip %}

{% if links.size &gt; 0 %}
&lt;ul&gt;{{ links }}&lt;/ul&gt;
{% endif %}


header.html [example usage]

&lt;header class="site-header"&gt;

  &lt;div class="wrapper"&gt;

    &lt;a class="site-title" href="{{ site.baseurl }}/"&gt;{{ site.title }}&lt;/a&gt;

    &lt;nav class="site-nav"&gt;
      &lt;a href="#" class="menu-icon"&gt;
        &lt;svg viewBox="0 0 18 15"&gt;
          &lt;path fill="#424242"
                d="M18,1.484c0,0.82-0.665,1.484-1.484,1.484H1.484C0.665,2.969,0,2.304,0,1.484l0,0C0,0.665,0.665,0,1.484,0 h15.031C17.335,0,18,0.665,18,1.484L18,1.484z"/&gt;
          &lt;path fill="#424242"
                d="M18,7.516C18,8.335,17.335,9,16.516,9H1.484C0.665,9,0,8.335,0,7.516l0,0c0-0.82,0.665-1.484,1.484-1.484 h15.031C17.335,6.031,18,6.696,18,7.516L18,7.516z"/&gt;
          &lt;path fill="#424242"
                d="M18,13.516C18,14.335,17.335,15,16.516,15H1.484C0.665,15,0,14.335,0,13.516l0,0 c0-0.82,0.665-1.484,1.484-1.484h15.031C17.335,12.031,18,12.696,18,13.516L18,13.516z"/&gt;
        &lt;/svg&gt;
      &lt;/a&gt;

      {% if page.url contains '/academy' %}
        {% include nav.html base_url='/academy/' max_depth=3 %}
      {% elsif page.url contains '/college' %}
        {% include nav.html base_url='/college/' max_depth=3 %}
      {% else %}
        {% include nav.html max_depth=2 %}
      {% endif %}

    &lt;/nav&gt;

  &lt;/div&gt;

&lt;/header&gt;