Do you rely on your URL?
Nick Christiansen
Engineering Team LeadAn essential and often forgotten part of state management includes URL design. The benefits of knowing when to use your URL as state, and what state to include in a URL can enable multiple benefits for both user and developer experiences.
For example, you build a brand new feature in your web app, using the latest state management tools. Users can sort it, filter it, paginate it, zoom in, zoom out, and find sweet new operational insights. Now, how do your users share it with everyone so they can see it?... Luckily you have relied on your URL. A simple copy and paste and a user can directly share the exact insight on your new feature.
The benefits of well designed web application URLs include:
- Allow users to navigate to different states using the browsers back and forward
- Seamless entry points to navigate through the application from different components or pages
- Granular entry points from external sources such as bookmarks or shareable links
- Decoupling of complex states using nested routing
- Enable deeper insights with analytics and logging tools
- Improved developer experience for hot loading by removing unnecessary state navigation
What is a URL?
A Uniform Resource Locator (URL) is a text string that specifies where a resource (web page) can be located on the Internet. For a web application, this can be broken down to a Scheme, a Domain Name, A Path to the file, and optional Parameters.
Domain Name
The Domain Name (Authority) is generally the entry point to any application. Commonly, the www
prefix is reserved for a marketing landing page, and a more distinct prefix is used for an application. These are generally referred to as a sub-domain. Depending on the use case, sub-domains can add flexibility to the domain name that can provide value both to the user and the business. Some example use cases include product separation, sandboxed environments or unique customer domains.
Examples: www.assignar.com
, dashboard.assignar.com
, testing.assignar.com
or customer.assignar.com
.
From a technical standpoint, the sub-domain can provide benefits for analytics, logging, debugging, and an added security meaure. It can provide an entry point to a specific product or product environment, which is helpful for providing insights or siloing issues. These benefits may be further realised with customer specific domain prefixes, these subdomains are helpful to clearly separate customer environments. For example, for Customer Success or Technical Support (i.e. Admin access), it is critical to know they are logged into the right account. Mistakes can easily happen when you are constantly moving between customer environment, while relying on a single domain for access to an application. Additionally, sub-domains can add a security layer as it creates isolation from any failure or vulnerability possibly experienced on another subdomain.
Path
The path represents an application's navigation to a feature, and is the start of a single source of truth for state management. A path can be used to link between different parts of an application, providing entry points to specific views. They are also the starting point of creating views which can be saved or shared.
When designing URL paths, for app consistency and readability, it is common to follow RESTFUL path naming conventions:
- Resource/entity represented as its noun:
order
- Entry by resource collection, to single resource entity:
/orders
β/orders/:orderId
- Sub-collections hold parent paths:
/orders/:orderId/roles
β/orders/:orderId/roles/:roleId
- The path represents state:
/orders
β view all orders,/orders/:orderId
β view order fororderId: xxx
These can be extended for actions and/or features: - Actions:
/orders/create
or/orders/:orderId/edit
- Features:
/scheduler
or/orders/:orderId/calendar
Conventionally, each path represents a different page. With tools like React and React-Router, single page applications can be built with the benefits of RESTFUL path naming using nested routing. Below shows an example of single page app with a resource collection β single resource entity β sub-collection navigation. The URL path is the source of truth, where for a user, these views can be shared or book marked. For a developer, a view and/or components can be separated by nested routes, only relying on the url for state, which can avoid unnecessary prop drilling.
Parameters
Parameters are optional extras that can be provided to a URL. They are separated by key value pairs, that can be used to perform additional logic before returning a resource. Conventionally they are used for viewing filtered, sorted, and/or paginated views. Depending on the feature, they can also be used for modals or view preferences, such, zoom and highlighting. The following are some examples of query parameters representing state:
- Filtering: search criteria β
/orders?search=thing
, return orders for specific project β/orders?projectId=123
- Sorting: sort orders by name β
/orders?orderBy=Name
- Pagination: show results after a specific entry β
/orders?after=last-end-cursor
Selection of what state should live in the parameters is important as it can quickly become complex. Conventionally, if it required to load data onto a page, that a user controls (i.e. filter, sort, pagination), it is an ideal candidate to live in the URL. If it is a user preference (i.e. scaling, sizing, zooming), local storage may be a better source of truth. Choosing what parameters live in the URL all depends on the feature and how the user(s) interact with it.
Using Parameters with Local Storage
It is common for features to persist state on certain views. For example, a feature with complex filtering, such as a table, would be hassle to have to refill the filter options if the user leaves the page and comes back (not using the browser history). If this is the case for a feature, the filters can be set to local storage, and used to pre-fill filtering upon return to a feature. How this works with query parameters needs to be considered, as this presents a duplication of state. Knowing and handling the primary source of state should clearly defined and separated.
If using parameters in a URL, this should be considered the primary state to the application. To persist this state upon return to a view, the URL parameters can then be set to local storage on change, and then be queried on first mount of the component or page.
A case to preference local storage over the url state might be user preferences. For instance, if a user prefers dark mode over light mode, or other ui/ux type preferences, these are probably better set to local storage or on the database. Since these type of preferences do not change the data being displayed, they can share their url without disturbing another users view.
Other Considerations
Some points to consider when designing URLs:
- Formating: Having an agreement across your organisation early on what format URLs might will look like will remove an unnecessary decision making later.
- Security: Should this param live in the URL and browser history and be shareable?
- Length: Whenever possible, shorten URLs by trimming unnecessary parameters. The max character count for a URL is 2,083. This could be exceeded with excessive use of base64 encoded data.
- Route migrations: Adding route redirects for users that may rely on deprecated routes.
- Legacy: How long will be your URL last? - Cool URIs don't change
Conclusion
Relying on your URL can improve both user and developer experience. Having a standard in your organisation, and following an aligned best practice will simplify decision making when building your next feature. A well designed URL can enable seamless entry to features with complex state, which can be shared as required. How a URL interacts with other state management tools, such as Local Storage, requires clear separation and interaction. Furthermore, for your product and developers a thoughtful URL will provide deeper insights from analytics, logging tools and hopefully reduce debugging time across features.