manage navigation and internal linking in large Jekyll repos
As your Jekyll site grows—more pages, more collections, more contributors—navigating through it becomes harder. Readers get lost, editors break links, and developers struggle to keep menus synced. Without centralized navigation and consistent link practices, your clean structure quickly turns chaotic.
Good navigation isn't just about usability; it's part of your repository's architecture. It needs to be structured, scalable, and dynamically generated wherever possible.
What are the challenges of managing navigation at scale?
In large Jekyll repos, navigation suffers from:
- Hardcoded menu links across multiple includes or layouts
- Outdated internal links after URL changes
- Broken links due to inconsistent naming or file movement
- No clear distinction between global and local navigation
- Difficulty in managing multi-level navigation structures
What’s the best way to centralize navigation data?
Use _data/nav.yml or a similar centralized data file to define your site menu. For example:
main:
- title: "Home"
url: "/"
- title: "Blog"
url: "/blog/"
- title: "Docs"
url: "/docs/"
children:
- title: "Getting Started"
url: "/docs/getting-started/"
- title: "API"
url: "/docs/api/"
This approach keeps navigation decoupled from layout logic. Editors can add or reorder menu items without touching HTML files.
How do you render structured navigation from data?
Use recursive loops in an include (e.g. _includes/nav.html) to display menu levels:
{% raw %}
{% for item in site.data.nav.main %}
-
{{ item.title }}
{% if item.children %}
{% for sub in item.children %}
- {{ sub.title }}
{% endfor %}
{% endif %}
{% endfor %}
{% endraw %}
This setup ensures any navigation update reflects across the entire site instantly.
How do you organize navigation per section or language?
Split your nav data file based on structure. For example:
_data/
├── nav.yml
├── nav_en.yml
├── nav_id.yml
├── docs_nav.yml
Then, in the layout or include, load navigation based on page language or section:
{% raw %}
{% assign nav = site.data["nav_" | append: page.lang ] %}
{% endraw %}
This allows each section (like docs, blog, support) or locale to have its own navigation structure, but all stay centralized and structured.
How should you structure internal links in content?
Hardcoded links like /docs/page.html can break when paths change. To prevent this:
- Use Jekyll’s
relative_urlandabsolute_urlfilters - Use permalinks consistently in front matter
- Use custom link includes when linking between dynamic collections
{% raw %}
<a href="{{ '/docs/getting-started/' | relative_url }}">Start here</a>
{% endraw %}
For collection-based navigation (e.g., case studies, interviews), use link generators based on page properties rather than static URLs.
How do you automate active menu state?
For dynamic navigation highlighting:
{% raw %}
<a href="{{ item.url }}">{{ item.title }}</a>
{% endraw %}
This provides visual feedback on current location, improving usability.
Can you auto-generate a table of contents (ToC) per page?
Yes. Use plugins (outside GitHub Pages) like jekyll-toc, or manually extract headings using Liquid loops if you control markup consistency.
{% raw %}
{% assign headers = content | split: '<h2' %}
{% for section in headers offset:1 %}
<li><a href="#{{ section | slugify }}">{{ section | truncatewords: 5 }}</a></li>
{% endfor %}
{% endraw %}
This builds a per-page outline which helps readers navigate long-form content.
How do you manage footer navigation or secondary links?
Treat footers as a separate navigation group in the data layer:
footer:
- title: "Privacy Policy"
url: "/privacy/"
- title: "Terms of Use"
url: "/terms/"
- title: "Sitemap"
url: "/sitemap.xml"
Then include this wherever needed with minimal duplication.
What’s the best practice for linking between collections?
If you have multiple content types (e.g., _posts, _docs, _guides), avoid hardcoded cross-links. Instead:
- Use the
urlproperty of pages dynamically - Use filters to find related pages by tag or category
{% raw %}
{% assign related = site.guides | where: "category", page.category %}
{% for item in related %}
<a href="{{ item.url }}">{{ item.title }}</a>
{% endfor %}
{% endraw %}
This allows links to update automatically when file names or permalinks change.
Conclusion: What does structured linking and navigation enable in Jekyll?
A clean internal link structure and data-driven navigation make large Jekyll sites scalable, user-friendly, and editor-proof. Instead of chasing broken links and updating dozens of includes, you define your structure once—and reuse it consistently.
The secret lies in separation: content lives in Markdown, navigation lives in data, and layout logic lives in includes. This way, structure isn’t just visual—it’s architectural.