Getting Started

The core of a single-page application is its router. The router is responsible for matching locations to its known routes and for powering navigation within the application.

The Router

A router is created using a history function and a routes array.

import { createRouter, prepareRoutes } from '@curi/router';
import { browser } from "@hickory/browser";

let routes = prepareRoutes([...]);
let router = createRouter(browser, routes);


Locations are represented using URLs. URLs are a combination of a pathname string, a query, and a hash.

The router provides a url method for automatically generating a URL. The method takes the name of the route and the route's params to generate the URL's pathname. Additionally, query and hash values can be provided.

let routes = prepareRoutes([
  { name: "Home", path: "" },
  { name: "Contact", path: "contact/:method" }
let router = createRouter(browser, routes);

let homeURL = router.url({ name: "Home" });
// "/"

let phoneURL = router.url({
  name: "Contact",
  params: { method: "phone" }
// "/contact/phone"

let queryURL = router.url({
  name: "Home",
  query: "value=7"
// "/?value=7"

By default, a query is a string, but you can also configure your history to use a query library.

import { parse, stringify } from "qs";
let router = createRouter(browser, routes, {
  history: {
    query: { parse, stringify }

let queryURL = router.url({
  name: "Home",
  query: { value: "6" }
// "/?value=6"

There are two types of navigation within a single-page application: in-app navigation (e.g. clicking a link) and platform navigation (e.g. clicking the back button or typing a URL in the address bar and hitting enter).

A Curi router object has a navigate method to let you navigate with code. The function takes an object with a url property of the URL to navigate to; this pairs well with the router's url method described above. There are also a number of other optional arguments to navigate.

  url: "/photo/1357/02468#comments"

Response Handlers

When Curi matches a location to a route, it creates a "response" object. Respons objects provide information about the route that matched.

Response handlers are functions that will be called when there is a new response. There are three types of response handlers: side effects, one time functions, and observers.

Side effects are passed to the router when you are creating it. These are best suited for non-rendering tasks. You can read more about them in the side effects guide.

let router = createRouter(browser, routes, {
  sideEffects: [scroll(), title(...)]

Response handlers registered with router.once will only be called one time. This is primarily useful for waiting for asynchronous actions to finish before the initial render.

let router = createRouter(browser, routes);
// wait for the initial route's async action to complete
router.once(() => {
  // this is not called until the initial response is ready
  // so we can safely render in here

Observers are passed to the router using router.observe. Unlike one time functions, these will be called every time there is a new response.

Render packages, like @curi/react-dom, use router.observe internally in order to re-render when there is a new response.

router.observe(({ response }) => {
  console.log('new response!', response);


Curi adapts its API to work with different UI libraries. You can check out the respective guides for the officially supported libraries to see how to use them.