Let’s get one thing straight: in Eleventy, your file system is your URL structure. It’s not a suggestion, it’s not a guideline—it’s the law. Forget complex routing tables or configuration nightmares for basic stuff. You put a file called my-rad-post.md in a folder named blog, and Eleventy, without any fuss, will build it out to /blog/my-rad-post/index.html. This is the core of its “convention over configuration” philosophy, and it’s brilliant because it’s simple. You can predict the final URL just by looking at your project’s folders.

But this beautiful, logical system is also the source of most beginner headaches. You’ll think you’re building a file at one URL and find it, bewilderingly, at another. Let’s dissect how it actually works so you can be the one in control.

The Golden Rule: Input -> Output

Eleventy has an input directory (commonly src or root) and an output directory (commonly _site or dist). The rule is: the directory structure inside your input folder is mirrored in your output folder. Markdown (.md) and template files (.njk, .liquid, etc.) become .html files.

If you have this structure:

src/
  blog/
    my-first-post.md

You get this output:

_site/
  blog/
    my-first-post/
      index.html

Wait, why did it become a folder with an index.html inside it instead of just my-first-post.html? Because Eleventy is, deep down, a good web citizen. Clean URLs—those without the .html extension—are considered better for SEO and user experience. /blog/my-first-post/ is just cleaner than /blog/my-first-post.html. It does this by default for pretty much all HTML files.

Sometimes the default is… wrong. Maybe you have a legacy system to match, or you just really hate clean URLs (you monster). This is where the permalink front matter comes in. It’s your override, your escape hatch. You can use it to completely dictate the final URL.

---
title: "My Weirdly Named Post"
permalink: "/articles/2023/weird-post.html"
---

This content will now be output to `_site/articles/2023/weird-post.html`. The original file location in `src` becomes completely irrelevant. The permalink property is absolute law.

You can also use liquid templates in your permalinks for dynamic power, which is incredibly useful for things like pagination or date-based structuring.

---
title: "A Dynamic Post"
date: 2023-10-26
permalink: "/blog/{{ date | year }}/{{ title | slug }}/"
---

This will output to `/blog/2023/a-dynamic-post/index.html`. The `slug` filter is your best friend here—it converts the title into a URL-safe string.

The url Filter and Absolute Paths

Here’s a common pitfall that will make you tear your hair out. You’re writing a post and want to link to another page. You think, “I know the URL, I’ll just write <a href="/blog/other-post/">”. This is a mistake.

What if you decide to change your structure? What if you add a base path for a GitHub Pages site? You’ll have to find and fix every single link. Instead, use Eleventy’s url filter. This filter takes the source file’s path and generates the final, absolute URL for you, respecting any permalink overrides you’ve set.

<!-- This is fragile and bad -->
<a href="/blog/my-other-post/">Read my other post</a>

<!-- This is robust and excellent -->
<a href="{{ '/src/blog/my-other-post.md' | url }}">Read my other post</a>

The url filter is the only way to be sure your links are future-proof. It’s a non-negotiable best practice.

The Index File Anomaly

There’s one glorious exception to the rules above: files named index.*. An index.md or index.njk file becomes the folder itself. It’s the homepage of that directory.

This file structure:

src/
  blog/
    index.md    # This defines the /blog/ page
    post-1.md   # This becomes /blog/post-1/

This is why your site’s root index.md becomes the homepage at /. It’s not magic; it’s just the rule applied consistently. Use this to your advantage. Want an “About” section with multiple sub-pages? Create an about/index.md for the main /about/ page and put its sub-pages (like about/team.md) right alongside it. The structure will be perfectly logical.