Skip to main content
Routers manage the synchronization between your search UI state and the browser URL. They enable users to share search results, use browser back/forward buttons, and bookmark search states.

Available Routers

history

The history router uses the browser’s History API (pushState and popstate) to manage URL synchronization.
import { history } from 'instantsearch.js/es/lib/routers';

const search = instantsearch({
  indexName: 'products',
  searchClient,
  routing: {
    router: history(),
  },
});

history() Options

windowTitle
Function
Function that returns the page title based on the route state.
history({
  windowTitle(routeState) {
    const query = routeState.query;
    return query ? `Search: ${query}` : 'Search';
  },
})
writeDelay
number
default:"400"
Time in milliseconds to wait before writing to the URL. This prevents adding too many entries to the browser history.
history({
  writeDelay: 300,
})
createURL
Function
Function to create a URL from the route state.
history({
  createURL({ qsModule, routeState, location }) {
    const { protocol, hostname, port, pathname, hash } = location;
    const queryString = qsModule.stringify(routeState);
    const portWithPrefix = port ? `:${port}` : '';
    
    if (!queryString) {
      return `${protocol}//${hostname}${portWithPrefix}${pathname}${hash}`;
    }
    
    return `${protocol}//${hostname}${portWithPrefix}${pathname}?${queryString}${hash}`;
  },
})
parseURL
Function
Function to parse the URL into route state.
history({
  parseURL({ qsModule, location }) {
    return qsModule.parse(location.search.slice(1), {
      arrayLimit: 99,
    });
  },
})
getLocation
Function
Function that returns the current location object. Useful for server-side rendering or testing.
history({
  getLocation() {
    return window.location;
  },
})
start
Function
Custom function to start listening for URL changes. Advanced use only.
history({
  start(onUpdate) {
    // Custom logic to listen for URL changes
    window.addEventListener('popstate', onUpdate);
  },
})
dispose
Function
Custom function to clean up when routing is disposed. Advanced use only.
history({
  dispose() {
    // Custom cleanup logic
  },
})
push
Function
Custom function to push URL changes. Advanced use only.
history({
  push(url) {
    window.history.pushState(null, '', url);
  },
})
cleanUrlOnDispose
boolean
default:"true"
Whether to clean up the URL when InstantSearch is disposed. Setting this to false preserves active refinements in the URL when closing modals or unmounting the component.
history({
  cleanUrlOnDispose: false,
})

Router Methods

Router instances provide these methods:

read()

Reads the current URL and returns the route state.
const router = history();
const routeState = router.read();

write(routeState)

Writes the route state to the URL.
router.write({
  query: 'laptop',
  refinementList: {
    brand: ['Apple'],
  },
});

createURL(routeState)

Creates a URL string from the route state.
const url = router.createURL({
  query: 'phone',
  page: 2,
});

onUpdate(callback)

Registers a callback for URL changes.
router.onUpdate((routeState) => {
  console.log('URL changed:', routeState);
});

dispose()

Cleans up event listeners and optionally cleans the URL.
router.dispose();

Examples

Basic History Router

import instantsearch from 'instantsearch.js';
import { history } from 'instantsearch.js/es/lib/routers';
import { simple } from 'instantsearch.js/es/lib/stateMappings';

const search = instantsearch({
  indexName: 'products',
  searchClient,
  routing: {
    router: history(),
    stateMapping: simple(),
  },
});

Custom Window Title

import { history } from 'instantsearch.js/es/lib/routers';

const search = instantsearch({
  indexName: 'products',
  searchClient,
  routing: {
    router: history({
      windowTitle(routeState) {
        const query = routeState.query || '';
        const brand = routeState.refinementList?.brand?.[0] || '';
        
        if (query && brand) {
          return `${query} - ${brand} | My Store`;
        }
        if (query) {
          return `${query} | My Store`;
        }
        return 'Search | My Store';
      },
    }),
  },
});

Custom URL Structure

import { history } from 'instantsearch.js/es/lib/routers';

const search = instantsearch({
  indexName: 'products',
  searchClient,
  routing: {
    router: history({
      createURL({ qsModule, routeState, location }) {
        const baseUrl = `${location.protocol}//${location.hostname}${location.port ? ':' + location.port : ''}`;
        
        // Create clean URLs like: /search/laptop/brand-apple/page-2
        const parts = [];
        
        if (routeState.query) {
          parts.push(routeState.query);
        }
        
        if (routeState.refinementList?.brand?.[0]) {
          parts.push(`brand-${routeState.refinementList.brand[0]}`);
        }
        
        if (routeState.page && routeState.page > 1) {
          parts.push(`page-${routeState.page}`);
        }
        
        return `${baseUrl}/search/${parts.join('/')}`;
      },
      
      parseURL({ location }) {
        // Parse URLs like: /search/laptop/brand-apple/page-2
        const parts = location.pathname.split('/').filter(Boolean);
        const routeState = {};
        
        if (parts[0] === 'search' && parts[1]) {
          routeState.query = parts[1];
        }
        
        parts.forEach(part => {
          if (part.startsWith('brand-')) {
            routeState.refinementList = {
              brand: [part.replace('brand-', '')],
            };
          }
          if (part.startsWith('page-')) {
            routeState.page = parseInt(part.replace('page-', ''), 10);
          }
        });
        
        return routeState;
      },
    }),
  },
});

Faster URL Updates

import { history } from 'instantsearch.js/es/lib/routers';

const search = instantsearch({
  indexName: 'products',
  searchClient,
  routing: {
    router: history({
      writeDelay: 100, // Update URL faster (default: 400ms)
    }),
  },
});

Preserve URL on Unmount

import { history } from 'instantsearch.js/es/lib/routers';

const search = instantsearch({
  indexName: 'products',
  searchClient,
  routing: {
    router: history({
      cleanUrlOnDispose: false, // Keep filters in URL when closing modal
    }),
  },
});

Server-Side Rendering

import { history } from 'instantsearch.js/es/lib/routers';

const search = instantsearch({
  indexName: 'products',
  searchClient,
  routing: {
    router: history({
      getLocation() {
        // Provide location for SSR
        return typeof window !== 'undefined'
          ? window.location
          : new URL('https://example.com/search');
      },
    }),
  },
});

Router Type

The router must implement this interface:
interface Router<TRouteState = UiState> {
  read(): TRouteState;
  write(routeState: TRouteState): void;
  createURL(routeState: TRouteState): string;
  onUpdate(callback: (routeState: TRouteState) => void): void;
  dispose(): void;
}

Custom Router Implementation

You can create a custom router by implementing the Router interface:
const customRouter = {
  read() {
    // Read state from custom storage (e.g., localStorage)
    const state = localStorage.getItem('searchState');
    return state ? JSON.parse(state) : {};
  },
  
  write(routeState) {
    // Write state to custom storage
    localStorage.setItem('searchState', JSON.stringify(routeState));
  },
  
  createURL(routeState) {
    // Create shareable URL
    const query = new URLSearchParams(routeState).toString();
    return `${window.location.pathname}?${query}`;
  },
  
  onUpdate(callback) {
    // Listen for state changes
    window.addEventListener('storage', (event) => {
      if (event.key === 'searchState') {
        callback(JSON.parse(event.newValue));
      }
    });
  },
  
  dispose() {
    // Cleanup
    window.removeEventListener('storage', this._onStorageChange);
  },
};

const search = instantsearch({
  indexName: 'products',
  searchClient,
  routing: {
    router: customRouter,
  },
});

Build docs developers (and LLMs) love