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

navigation-ponyfill

v0.2.0

Published

A ponyfill (polyfill) for the browser Navigation API

Readme

navigation-ponyfill

A ponyfill (polyfill) for the browser Navigation API that enables tracking of browser history navigation, including (reasonably) reliable detection of when the user can navigate backwards in a single-page application.

navigation-ponyfill has zero runtime dependencies and will defer to the native Navigation on window.navigation when available.

What's a ponyfill?

A ponyfill is like a polyfill, but instead of patching the global environment, it exports the functionality as a module.

Unfortunately, navigation-ponyfill is not entirely side-effect free due to how it works.

Why?

My most immediate concern when implementing this ponyfill was to support "back" buttons in single-page applications. It is desirable to use the history.back() method in such cases so that the behavior is in-line with the browser's back button (and swiping on mobile). However, you don't want to bounce the user off your application if they came from elsewhere. In this case, it is preferable to navigate to a fallback URL instead.

It's a UI element seen in many applications (Instagram, Twitter/X, Bluesky, etc.) and while it's a slam-dunk to implement with Navigation, it's a minefield of edge-cases and tricky to get right using the History API. It is easiest to implement if you can just pass the previous URL with history.pushState({ previousUrl }, '', newUrl), but that's not something we can readily hook into in all frameworks (looking at you, Next.js).

Installation

npm install navigation-ponyfill

TypeScript projects require installation of @types/dom-navigation as well.

npm install -D @types/dom-navigation

Quick Start

import { navigation } from 'navigation-ponyfill'

// Check if back navigation is available
if (navigation.canGoBack) {
  history.back()
}

// Access the current entry and history entries
console.log('Current URL:', navigation.currentEntry?.url)
console.log('History length:', navigation.entries().length)

// Listen for navigation changes
navigation.addEventListener('currententrychange', (event) => {
  console.log('Navigated:', event.navigationType) // 'push' | 'replace' | 'traverse'
  console.log('From:', event.from.url)
  console.log('To:', navigation.currentEntry?.url)
})

Entry Points

The package provides two entry points:

Default (with side effects)

import { navigation } from 'navigation-ponyfill'

Returns the native window.navigation if available, otherwise patches history.pushState and history.replaceState and returns the ponyfill. Use this for most applications.

Core (side-effect-free on import)

import { createNavigation, Navigation } from 'navigation-ponyfill/core'

// No side effects until you call createNavigation()
const navigation = createNavigation()

No automatic patching—you control when and how the Navigation instance is created. Useful for testing or advanced use cases.

API Reference

navigation

import { navigation } from 'navigation-ponyfill'

A singleton that provides the Navigation API. This can be either:

  • The native window.navigation if the browser supports the Navigation API
  • The ponyfill Navigation instance as a fallback

Both share a common interface for the properties and methods below.

Properties

  • currentEntry: NavigationHistoryEntry | null — The current history entry.
  • canGoBack: boolean — Whether the user can navigate backwards in this session.
  • canGoForward: boolean — Whether the user can navigate forwards in this session.

Methods

  • entries(): NavigationHistoryEntry[] — Returns an array of all history entries in the current session.

Events

  • currententrychange — Fired when navigation occurs via pushState, replaceState, hash changes, or browser back/forward.

Navigation

The ponyfill implementation, available via createNavigation({ force: true }) or when the native API is unavailable.

Additional Methods

  • destroy(): void — Restores original history methods and removes event listeners. Use this for cleanup in tests or when the ponyfill is no longer needed.

NavigationCurrentEntryChangeEvent

Event object passed to currententrychange listeners.

interface NavigationCurrentEntryChangeEvent extends Event {
  readonly from: NavigationHistoryEntry // Previous history entry
  readonly navigationType: NavigationType | null // How the navigation occurred - reload not supported
}

NavigationHistoryEntry

Represents a history entry.

interface NavigationHistoryEntry extends EventTarget {
  readonly id: string // Unique identifier for this entry
  readonly key: string // Stable key that persists across replace operations
  readonly index: number // Position in the entries list (-1 if disposed)
  readonly url: string | null // Full URL of the entry
  readonly sameDocument: boolean // Whether this was a same-document navigation
  getState(): unknown // Returns a clone of the state for this entry
}

Events

  • dispose — Fired when the entry is removed from the history stack (e.g., on replace, or when navigating to a new page after going back).

NavigationType

type NavigationType = 'push' | 'replace' | 'traverse' | 'reload'
  • pushhistory.pushState() was called
  • replacehistory.replaceState() was called
  • traverse — Browser back/forward navigation (popstate)
  • reload — Page reload (not currently emitted, included for alignment with native types)

createNavigation(options?)

Factory function to create a Navigation instance.

function createNavigation(
  options?: CreateNavigationOptions,
): Navigation | NativeNavigation
function createNavigation(options: { force: true }): Navigation

By default, returns the native window.navigation if available, otherwise returns the ponyfill. Use force: true to always get the ponyfill instance.

CreateNavigationOptions

type CreateNavigationOptions = {
  force?: boolean
  history?: History | HistoryShim
}
  • force — When true, always returns the ponyfill Navigation instance, even if the native Navigation API is available. Default: false (prefers native when available).
  • history — Custom History object to use. Defaults to window.history in browser environments, or a no-op HistoryShim during SSR.

Examples

import { createNavigation } from 'navigation-ponyfill/core'

// Default: uses native Navigation API if available, otherwise ponyfill
const navigation = createNavigation()

// Force ponyfill even when native is available (useful for testing)
const navigation = createNavigation({ force: true })

// Type narrowing for ponyfill-specific methods
if ('destroy' in navigation) {
  navigation.destroy()
}

// Custom history object (useful for testing)
const navigation = createNavigation({ force: true, history: customHistory })

Framework Integration

Next.js

See the Next.js example for a complete integration with React context and hooks.

SSR Support

The ponyfill includes a HistoryShim that provides a no-op implementation for server-side rendering. When window is not available, createNavigation() automatically uses the shim.

How It Works

The ponyfill monkey-patches history.pushState and history.replaceState, augmenting the state object with navigation metadata:

history.state = {
  ...yourState,
  __NAVIGATION_PONYFILL: {
    entryId: 'abc123',
    entryKey: 'def456',
  },
}

It maintains a stack of NavigationHistoryEntry objects persisted to sessionStorage, allowing entries() and currentEntry to survive page reloads. It also listens for popstate events to track browser back/forward navigation and hash changes.

Because of the use of history.state and sessionStorage, the ponyfill even works in multi-page applications (MPAs).

Caveats

Unsupported APIs

  • NavigationCurrentEntryChangeEvent with navigationType: 'reload' - this is impossible for us to detect.
  • NavigationHistoryEntry.sameDocument does not work -- we always set it to true to maintain the same type signature with native API. It is impossible for us to determine in the ponyfill if an entry is from the same document (page) or not.

Multi-Page Applications (MPAs)

  • While the ponyfill works for MPAs, it must be loaded on every page. If it's not, its state might become corrupted. This is untested.

State in History API calls must be an object or nullish

Normally you can call history.pushState(state, '', url) with any serializable value for state (including boolean, string, array, etc.). Because the ponyfill merges your state with its own metadata, the state must be an object or nullish (null/undefined).

Links