230 words, 2 min read

When working with redirects, internal links, or LiveView navigation, you often want to turn an absolute URL into a relative one. Given a URL like:

https://example.com/some/path?page=2#section

the goal is to end up with:

/some/path?page=2#section

The non-idiomatic approach

A common first attempt is to manually concatenate path, query, and fragment after parsing the URL. While this works, it pushes URL semantics into string logic and is easy to get wrong.

Elixir’s standard library gives us a cleaner option.

The idiomatic solution using URI

The URI module is designed so that parsing and serialization are inverse operations. The trick is to parse the URL, drop the parts you don’t need, and convert it back to a string.

url = "https://example.com/some/path?page=2#section"
relative =
url
|> URI.parse()
|> Map.take([:path, :query, :fragment])
|> then(&struct(URI, &1))
|> URI.to_string()

This produces:

"/some/path?page=2#section"

Why this approach works well

  • No manual handling of ? or #
  • Correct behavior when query or fragment is missing
  • Clear intent: keep only the parts relevant for a relative URL
  • Uses only Elixir’s standard library

A small helper function

If you need this in more than one place, wrapping it in a helper keeps your codebase tidy:

def relative_url(url) do
url
|> URI.parse()
|> Map.take([:path, :query, :fragment])
|> then(&struct(URI, &1))
|> URI.to_string()
end

This pattern keeps URL handling declarative and avoids brittle string manipulation, which is exactly what the URI module is there for.