Skip to main content

Routing URLs

Overview

Synchronizing your UI with the browser URL is a good practice. We currently take advantage of this practise on our PLPS and it is something we should consider for our new search experience. It allows your users to take one of your results pages, copy the URL, and share it. It also improves the user experience by enabling the use of the back and next browser buttons to keep track of previous searches.

React InstantSearch Hooks and InstantSearch.js provides the necessary APIs to enable us to synchronize the state of our search UI (refined widgets, current search query, the uiState) with any kind of storage. This is possible with the routing option.

This guide will address the different ways to handle routing as well as highlighting some experiments in our proof of concept.

Basic ULRs

Out of the box InstantSearch lets you enable URL synchronization by setting the routing to true. In our example we pass an object but this can be set to true. Using the routing option as an object, you can configure:

  • windowTitle: a method to map the routeState object returned from stateToRoute to the window title.
  • createURL: a method called every time you need to create a URL. When:
    • You want to synchronize the routeState to the browser URL
    • You want to render a tags in the menu widget
    • You call createURL in one of your connectors’ rendering methods.
  • parseURL: a method called every time the user loads or reloads the page, or clicks on the back or next buttons of the browser.

The types for the router and the createRouterMiddleware can be found in the resources section.

const RAPHA = {
APP_ID: '...',
SEARCH_API_KEY: '...',
INDEX_NAME: 'Initial_test'
};

<InstantSearch
searchClient={client}
indexName={RAPHA.INDEX_NAME}
routing={{
router: history({
getLocation() {
if (typeof window === 'undefined') {
return new URL(url!) as unknown as Location;
}

return window.location;
},
})
}}
>

Assume the following search UI state:

  • Query: "Legion"
  • Menu:
    • categories: "Jerseys"
  • Refinement List:
    • color: "Black", "Dark Navy"
  • Page: 2

https://search.rapha.cc/?Initial_test[query]=Legion&Initial_test[menu][categories]=Jerseys&Initial_test[refinementList][color][0]=Black&Initial_test[refinementList][color][0]=Dark Navy&Initial_test[page]=2

Rewrite URLs

The default URLs that InstantSearch generates are comprehensive, however, the more widgets we introduce, this will generate more noise in the URL. We may want to decide what goes in the URL and what doesn’t, or even rename the query parameters to something that makes more sense.

The stateMapping option defines how to go from InstantSearch’s internal state to a URL, and vice versa. This is what we can use to rename query parameters as well as choose what to include or exclude from the URL.

<InstantSearch
searchClient={client}
indexName={RAPHA.INDEX_NAME}
routing={{
router: history({
getLocation() {
if (typeof window === 'undefined') {
return new URL(url!) as unknown as Location;
}

return window.location;
},
}),
stateMapping: {
stateToRoute(uiState) {
// ...
},
routeToState(routeState) {
// ...
},
},
}}
>

InstantSearch manages a state called uiState. It contains information like query, facets, or the current page, including the hierarchy of the added widgets.

To persist this state in the URL, InstantSearch first converts the uiState into an object called routeState. This routeState then becomes a URL. Conversely, when InstantSearch reads the URL and applies it to the search, it converts routeState into uiState. This logic lives in two functions:

stateToRoute: converts uiState to routeState. routeToState: converts routeState to uiState.

If we look back at our previous example of search UI state:

  • Query: "Legion"
  • Menu:
    • categories: "Jerseys"
  • Refinement List:
    • color: "Black", "Dark Navy"
  • Page: 2

This translates into the following uiState:

{
"indexName": {
"query": "Legion",
"menu": {
"categories": "Jerseys",
},
"refinementList": {
"color": ["Black", "Dark Navy"],
},
"page": 2,
},
}

You can implement stateToRoute to flatten this object into a URL, and routeToState to restore the URL into a UI state. An example of this can be found in the resources section.

SEO-friendly URLs

As good as the out of the box implementation on URLs is, it does lack an element of being human friendly and SEO friendly. Manipulating the URL path is a common ecommerce pattern that lets you better reference your result pages. An example of this would be:

https://search.rapha.cc/Jerseys/?query=legion&page=2&color=Black&color=Dark+Navy or https://search.rapha.cc/jerseys/?query=legion&page=2&color=black&color=dark+navy

This kind of URL lends itself more to a PLP as it contains the category slug in the url. You can also see from this implementation that the index name has been removed. However, we are able to add categories to search to refine the results which would in turn make search and plp more interchangable.

SEO

For search results to be part of search engines results, we have to be selective. Adding too many search results inside search engines is considered as spam.

To add our most searched categories, we can create a robots.txt to allow or disallow URLs from being crawled by search engines.

Here’s an example based on the previously created URL scheme.

User-agent: *
Allow: /search/jerseys/
Allow: /search/bibs/
Disallow: /search/
Allow: *

Resources