The URL can be a powerful tool for sharing and managing the state of an app, but it's often overlooked or misunderstood. When properly done, the typical CRUD app with read/write/edit/delete functionality doesn't need much more state management than the URL. Understanding the anatomy of the URL and what type of state should live where can help you build a better user experience.
The anatomy of a URL
URL stands for Uniform Resource Locator, which is a standard way to identify the location of a resource on the web.
A URL consists of five distinct parts:
Protocol
Authority
Path
Query
Fragment
Check out this writeup on MDN docs if you're interested in learning about these parts in depth, but for state management purposes we primarily care about path
, query
, and fragment
.
Path
The path is a forward slash "/" delimited address of where a resource resides.
An example of a path is
/blog/2023/01/01/name-of-article.html
Having a well-organized structure for managing paths can provide users with a better experience as it allows them to understand where they're located and potentially help them see where else they're able to go. It also makes it easier during development to generate links to other resources.
Every part of a path should be accessible
The structure of the URL path is a hint to the user of not just what resource they're currently viewing, but what resources are also available. A user should be able to access each part or layer of the path and be able to view that part of the resource.
With the /blog/2023/01/01/name-of-article.html
example, a user would expect to be able to read the article. The path also exposes information to the user that they should be able to access other articles published on a particular day, month, or year. If the /name-of-article.html
is removed and the user navigates to /blog/2023/01/01
then the user would expect to see a list of all articles published on that day, navigating to /blog/2023/01
would be expected to show a list of all articles published that month and visiting /blog
would be expected to show a list of all articles published.
If you have a path like /users/12345/settings
, you should also make sure you appropriately render content for /users
and /users/12345
in addition to /users/12345/settings
.
Paths should be consistent and predictable
If every part of a path is accessible, then it's important to ensure that every path is organized consistently and is predictable.
If /users/12345/settings
displays the settings for user 12345, and /users/12345
displays a summary of user 12345, and /users
displays a list of all users; then a user might be confused if they were to see /11111/product/inventory
.
A good structure to follow is:
/<resource-name>/<resource-id>/<feature-or-association>/<association-id>
Some examples:
/products/1234/variants/5678
/products/1234
/products/1234/variants
/products
Query Parameters
While the path generally represents information related to what resource is meant to be displayed, the query represents how the resource should be displayed. The query is where resource modifiers and application state should be stored. This includes things like filters, search terms, and pagination info but can also include meta information used for analytics purposes. By default, HTML forms will also add form values to the query after a user submits a form.
Query parameters begin with a question mark (?) and consist of a key/value pair with an equal sign (=) separating the key and the value, and an ampersand "?" in between each pair.
For example:
/resources?pageNumber=4&searchTerm=books&maxPrice=10000
Store as much application state in the query as possible
There are a lot of benefits to the user when application state lives in the URL, for example, they can refresh the page without losing state, they can bookmark the page and access it later, or share it with someone else and make sure the other person sees the same thing they do. Any state that modifies the resource defined in the path should be stored in the URL as a query parameter.
A good rule of thumb is that if the user has access to modify the state, and that state is not persisted to the server via an API request, then it should be stored in the URL as a query parameter.
Fragment
A fragment appears at the end of the URL and is prefixed with a hashtag (#). This is used in what is referred to as "anchor links" or "jump links", and is used to focus on a particular element on the page. If the page has an element with an id
matching the fragment in the URL, then the page is rendered with that element at the top of the screen. This is particularly useful for things like blogs or pages with lots of information as users can be directed to the most relevant place within a page instead of forcing them to find it on their own.
This page assigns an id
to each section title, meaning you can navigate to https://webdev.tips/what-state-should-be-stored-in-the-url#fragment and the page will render with this section at the very top.
Use human-readable IDs for fragments that are exposed to users
Fragments should be readable and understandable to the average user, seeing a link like https://webdev.tips/what-state-should-be-stored-in-the-url#sdfsd2342342fsdaf23423 doesn't promote confidence as it doesn't tell the user where they'll navigate to if they were to click on it.
Ensure fragments work as expected
The browser typically handles navigating to fragments automatically, but if you're using a frontend framework then there's a chance your setup doesn't support fragments. Make sure to check that they work as expected.
Key Takeaways
The path should include the "what" that's rendered.
Query parameters should include all user-defined modifications of the "what" that's rendered.
The fragment should allow users to jump to specific places within the page.