npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

piconav

v0.0.4

Published

JS SPA nav library.

Readme

What is piconav?

piconav is a tiny library that keeps the browser URL in sync with your JS state for Single Page Applications.

It also syncs the document title, canonical URL and meta description.

It aims to allow Google indexing of SPA's to prevent the need for server side rendering / hydrating.

Why did you make piconav?

  • As a fast way to add SPA URL navigation to my prototypes.

    • React Router seems complicated and the API changes often.
    • I do not like the idea of using non-display JSX elements to add meta data to nodes (as React Router does with Route).
  • Clear API.

    • The standard browser APIs around single page navigation are not very clear so I created piconav to document their usage.
    • The document title, canonical URL and meta description are outside the control of the JSX DOM so have to updated directly with the DOM API.
  • SEO.

    • To document my experiments with Googlebot's indexing of SPA apps.
    • From experience I have found Google will index your JS pages if you load the data in a blocking script tag so there is no fetch latency after your initial JS loads.

Why should I use piconav?

  • Easy to use.
    • Use piconav if you want to get something up and running quickly.
  • Easy to replace.
    • Minimal API.
    • piconav is very simple.
    • You can copy or customize the functionality quickly.

How does piconav work?

There are four broad variables in JS and the DOM that need to be kept in sync:

  1. JS state
    • E.g. Your React store.
  2. JSX DOM.
    • Your state converted into DOM elements via JSX.
  3. URL
    • The browser address bar that the user types into, and which is changed on forward/back browser button presses.
  4. Document.
    • E.g the title, <link> meta and canonical URL values.

There are two sources of navigation:

  1. Browser.
    • Any navigation caused by the browser chrome UI (including swipe gestures for forward and back).
  2. JS.
    • Any navigation triggered by handling of a click event or other JS event.

piconav observes a navigation from one of the sources, and makes sure each of the four variables are in sync.

You need to integrate piconav into your app by passing it function callbacks - code examples below.

Generally, this is what happens when a navigation happens from each of the two sources:

Note: Indentation roughly shows the function call stack, although with observables it might not be exact as there will be some Mobx functions in the stack also.


  • JS Event (e.g click)
    • (1) Update state
      • (2) Update JSX DOM
      • nav(navEvent)
        • (3) Update URL
        • (4) Update Document

  • Browser event (e.g. pasting a new URL into the address bar)
    • (3) Update URL
      • events.browser.after(url, params)
        • (1) Update state
          • (2) Update JSX DOM
          • (4) Update Document.

How to add piconav to your JS project.

Install: yarn add piconav or npm install piconav.

1. Create a nav.js to add configuration and event handlers to piconav.

import {on, off} from "piconav";
import {storeIns} from "./../stores/store";

const resetPrimaryScroll = () => {
    document.querySelector("html").scrollTo(0, 0);
};

const {nav, navByBrowser, updateDoc} = on({
    site: {
        canonicalDomain: `https://example.com`
    },
    events: {
        js: {
            after: ({data}) => {
                if (data !== null && "type" in data && data.type !== "home") {
                    resetPrimaryScroll();
                }
            }
        },
        browser: {
            after: (url, params) => {
                // You should update your store state to match the incoming `url` and `params`.
                return storeIns.onBrowserNav(url, params);
            }
        }
    }
});

export {
    nav,
    navByBrowser,
    updateDoc
}

2. Import nav.js to your store, map JS state to document state.

When the user navigates via browser:

  • events.browser.after(url, params) is called.
    • Your code is expected to call updateDoc(navEvent) soon after.

When the user navigates via JS click:

  • Call nav(navEvent)

Example using a MobX store:

@observable curUrl = null;

// This is a public method on an observable store that your app uses to navigate.
// Use `curUrl` state to render the correct page in JSX.
// `src` should equal `browser` when `nav` is called from the `events.browser.after` callback. 
nav(appSpecificData, src = "js") {
    this.curUrl = {
        url: "/a/b/c",
        title: "A Title",
        metaDesc: "A page description",
        data: appSpecificData,
        src // equals "js" or "browser"
    };
}

Calling piconav functions after your app navigates to a new URL:

// Using `observe` on `curUrl` allows calling `nav` and `updateDoc` in a single place (instead of many call sites).
const disposer = observe(storeIns, "curUrl", ({oldValue, newValue}) => {
    const {url, title, metaDesc, data, src} = newValue;

    const navEvent = {
        url,
        doc: {
            title,
            metaDesc
        },
        
        // This can be any object, and will be passed to `events.js.after` to allow custom logic.
        data
    };

    if (src === "js") {
        nav(navEvent);
        return;
    }

    if (src === "browser") {
        updateDoc(navEvent);
        return;
    }
});

3. Run navByBrowser on initial page load.

// Will call the `events.browser.after` callback which should set your JS state to match that of the current browser URL.
// Note: this is best done before mounting your JSX as the first JSX render will be the correct page and not empty containers.
// - This ensures Googlebot snapshots the correct HTML.
navByBrowser();