Recursive (sub-)navigation with Jekyll 3.0

Jekyll 3 is stable, and a lot of the sites I help manage that use Jekyll have all updated and a lot of them had some hefty customization’s but with Jekyll 3.0 customization is easier. Here is how you can go about making recursive navigation with Jekyll 3.0 with minimal code:

_plugins/nav.rb

Jekyll::Hooks.register :site, :pre_render do |site, payload|
  payload["nav"] = []

  site.pages.each do |val|
    if val.url.gsub(/\A\//, "").split("/").size.between?(0, 1)
      payload["nav"] << Drops::NavItem.new(
        val, payload
      )
    end
  end
end

_plugins/drops/nav_item.rb

module Drops
  class NavItem < Liquid::Drop
    extend Forwardable

    def_delegator :@page, :data
    def_delegator :@page, :url

    # --
    # Initialize a new instance.
    # --
    def initialize(page, payload)
      @payload = payload
      @page = page
    end

    # --
    # The page title.
    # --
    def title
      @page.data.title
    end

    # --
    # The Sub-Pages.
    # --
    def pages
      return @payload["nav"] if url == "/"
      comp_ary = split(url)

      @page.site.pages.each_with_object([]) do |page, out|
        if page.url != "/" && (url_ary = split(page.url)) != comp_ary && \
            url_ary[0..-2] == comp_ary

          out << self.class.new(page, @payload)
        end
      end
    end

    # --
    # Whether or not we have sub-pages.
    # --
    def sub_pages?
      size > 0
    end

    # --
    # The size of the pages.
    # --
    def size
      pages.size
    end

    # --
    # Split the URL.
    # --
    private
    def split(url)
      url = url.gsub(/\A\//, "")
      url = url.chomp(File.extname(url))
      url.split("/")
    end
  end
end

_includes/nav.html

<ul>
  {% for _page in looping %}
    {% capture url %}{{ _page.url | prepend:site.baseurl }}{% endcapture %}
    {% assign title = _page.data.title %}

    <li>
      <a href="{{ url }}">{{ title }}</a>
      {% if _page.sub_pages? %}
        {% assign looping = _page.pages %}
        {% include nav.html %}
      {% endif %}
    </li>
  {% endfor %}
</ul>

Now in your main layout do this:

{% looping = site.nav %}
{% include   nav.html %}