15.5 URL Functions: absURL, relURL, urlize, anchorize
Right, let’s talk about making Hugo actually talk to the web. Because let’s be honest, a static site that can’t link to its own pages or format a URL correctly is about as useful as a chocolate teapot. Hugo’s URL functions are your toolkit for making sure that doesn’t happen. They’re the difference between a href="/about" that works on localhost and explodes when you deploy to a subdirectory, and one that just works, everywhere.
The Absolute vs. Relative Debate: absURL and relURL
This is the big one. The core concept you need to grip like the steering wheel of a fast car. absURL and relURL are your primary weapons for constructing robust URLs, and which one you use depends entirely on where the link is going.
Use absURL for any URL that leaves your site. This includes your CSS, JS, images, feeds, and links to external websites. Why? Because if your site is hosted at https://example.com/my-blog/, a relative link like href="/css/style.css" will break spectacularly. The browser will try to fetch https://example.com/css/style.css instead of https://example.com/my-blog/css/style.css. absURL fixes this by prepending your base URL, defined in your config.toml, to the path you provide.
<!-- This is the right way. Do this. -->
<link rel="stylesheet" href="{{ "css/main.css" | absURL }}">
<!-- Output: <link rel="stylesheet" href="https://example.com/my-blog/css/main.css"> -->
<!-- For the love of all that is holy, don't do this -->
<link rel="stylesheet" href="/css/main.css">
Conversely, use relURL for links within your own site. It’s smarter than a plain relative path because it respects any baseURL subpath. It prepends the subdirectory part of your baseURL but not the full scheme+host. This is perfect for internal navigation.
<!-- Hugo will handle the '/my-blog' part for you -->
<a href="{{ "posts/my-great-post" | relURL }}">Read my great post</a>
<!-- Output (if baseURL is "https://example.com/my-blog/"): -->
<!-- <a href="/my-blog/posts/my-great-post">Read my great post</a> -->
The Pitfall: The most common mistake is using absURL for everything. It works, but it’s sloppy. For internal links, you’re forcing the browser to do a full DNS lookup and connection handshake for every page load, sacrificing performance for no reason. Use the right tool for the job.
Making Text URL-Friendly: urlize and anchorize
These two functions are often confused because their output is frequently identical. But their purpose is subtly different.
urlize is your go-to for creating URL slugs from arbitrary text. It’s what you’d use if you were generating a permalink for a post title. It lowercases everything, replaces spaces with hyphens, and strips out any non-ASCII or dodgy characters.
{{ "My Fantastic Post & Why You'll ❤️ It" | urlize }}
<!-- Output: my-fantastic-post-why-youll-love-it -->
anchorize, on the other hand, is specifically for creating HTML anchor IDs (the id attribute you link to with #fragment). The rules for these are stricter, defined by the HTML spec. anchorize is more aggressive. It not only does what urlize does, but it also, crucially, prepends a letter or number if the first character isn’t one. This is because an HTML ID cannot start with a punctuation mark or a hyphen.
{{ "#1 Reason to Use Hugo" | anchorize }}
<!-- urlize would output: #1-reason-to-use-hugo (INVALID ID) -->
<!-- anchorize outputs: 1-reason-to-use-hugo (VALID ID) -->
The Insight: Think of it this way: use urlize for the entire URL path. Use anchorize specifically for the id="" attribute in your headings. If you’re generating table-of-contents anchors, anchorize is what you want.
The Forgotten Workhorse: ref and relref
While not in your list, I’d be remiss if I didn’t mention the ref and relref functions. These are Hugo’s secret sauce for internal linking. You don’t have to manually construct the path to another page; you just point to its unique identifier (usually the filename without the extension or the slug), and Hugo figures out the correct URL for you. It’s like magic, but the good kind that doesn’t break.
<!-- Find the page with .File.Path "content/posts/2021-01-01-my-post.md" -->
<a href="{{ relref . "posts/my-post" }}">Link to my post</a>
relref gives you a relative URL (perfect for the same site), and ref gives you an absolute one. This is the most robust way to link within your site, as it’s immune to changes in your content structure. If you move the post, the link still works. It’s a clear winner over manually typing {{ "posts/my-post" | relURL }}. Use it. Your future self will thank you when you do that big content reorganization.