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

elm-url-navigation-port

v1.1.0

Published

JS companion for mpizenberg/elm-url-navigation-port — port-based SPA navigation for Elm

Readme

elm-url-navigation-port

Port-based SPA navigation for Elm's Browser.element.

Demo app using this package

Use this instead of Browser.application when you need URL routing in embedded Elm apps, micro-frontends, or any context where you want full control over history management. I would often suggest staying on Browser.element for its simplicity and flexibility, as well as its better compatibility with external libraries and browser extensions. As a bonus, this package enables pushing state objects to the browser history API, allowing for more complex navigation patterns, such as multi-step wizards not changing the url.

Note: The browser's History API only accepts same-origin URLs. This package uses AppUrl from lydell/elm-app-url to represent navigation targets, which produce relative URL strings by construction — always same-origin.

Installation

Elm side:

elm install mpizenberg/elm-url-navigation-port

JS side:

npm install elm-url-navigation-port

Setup

1. Declare ports in your Elm app

port module Main exposing (main)

import Navigation as Nav

port navCmd : Nav.CommandPort msg
port onNavEvent : Nav.EventPort msg

2. Wire them up in JavaScript

<script type="module">
  import * as Navigation from "elm-url-navigation-port";

  const app = Elm.Main.init({
    node: document.getElementById("app"),
    flags: location.href,
  });

  Navigation.init({
    navCmd: app.ports.navCmd,
    onNavEvent: app.ports.onNavEvent,
  });
</script>

3. Use in your Elm code

import AppUrl exposing (AppUrl)
import Navigation as Nav

-- Subscribe to navigation events
subscriptions : Model -> Sub Msg
subscriptions _ =
    Nav.onEvent onNavEvent GotNavigationEvent

-- Navigate
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        NavigateTo appUrl ->
            ( model, Nav.pushUrl navCmd appUrl )

        GotNavigationEvent event ->
            ( { model | page = route event.appUrl }, Cmd.none )

Five navigation patterns

1. Page navigation — pushUrl

Standard SPA navigation. Creates a history entry and notifies Elm of the new URL. The browser back button works via popstate.

Nav.pushUrl navCmd (AppUrl.fromPath [ "about" ])

2. Page navigation with state — pushUrlWithState

Like pushUrl, but also attaches a state object to the history entry. Use this when you need metadata alongside a URL change (e.g. scroll position, referrer context).

Nav.pushUrlWithState navCmd
    (AppUrl.fromPath [ "product", "42" ])
    (Encode.object [ ( "scrollY", Encode.int 250 ) ])

The state arrives in event.state on back/forward navigation, just like with pushState.

3. State-based navigation — pushState

Push a state object without changing the URL. Useful for wizard steps or tab flows that should support the back button but don't need distinct URLs.

Nav.pushState navCmd
    (Encode.object [ ( "wizardStep", Encode.int 2 ) ])

On back-button press, the state object arrives in event.state. Decode it in your GotNavigationEvent handler:

GotNavigationEvent event ->
    let
        step =
            Decode.decodeValue (Decode.field "wizardStep" Decode.int) event.state
                |> Result.withDefault 1
    in
    ( { model | page = Wizard (intToStep step) }, Cmd.none )

4. History traversal — back / forward

Navigate backward or forward through the session history, like the browser's back and forward buttons. Supports jumping multiple steps at once.

Nav.back navCmd 1     -- go back one page
Nav.back navCmd 2     -- go back two pages
Nav.forward navCmd 1  -- go forward one page
Nav.forward navCmd 2  -- go forward two pages

The existing popstate listener handles the resulting navigation event automatically — no extra wiring needed.

5. Cosmetic URL update — replaceUrl

Update the URL bar without creating a history entry and without notifying Elm. The model stays the source of truth. Use this for display or shareability (e.g. fragments, counters).

Nav.replaceUrl navCmd
    { path = [ "about" ]
    , queryParameters = Dict.empty
    , fragment = Just (String.fromInt counter)
    }

Flags

Configure your server to always serve your root index.html file whatever the actual url that was provided to your server. On cloud platforms providing static servers, such as Cloudflare Pages, there is usually an option for this. Then pass location.href as a flag so Elm can route the initial page:

main : Program String Model Msg
main =
    Browser.element
        { init = init
        , update = update
        , subscriptions = subscriptions
        , view = view
        }

init : String -> ( Model, Cmd Msg )
init locationHref =
    ( { page =
            Url.fromString locationHref
                |> Maybe.map (AppUrl.fromUrl >> route)
                |> Maybe.withDefault NotFound
      }
    , Cmd.none
    )

Link clicks

Prevent default on internal links to avoid full page reloads:

import AppUrl exposing (AppUrl)
import Json.Decode as Decode

navLink : AppUrl -> String -> Html Msg
navLink appUrl label =
    a [ href (AppUrl.toString appUrl), onClickPreventDefault (NavigateTo appUrl) ]
        [ text label ]

onClickPreventDefault : msg -> Attribute msg
onClickPreventDefault msg =
    Html.Events.preventDefaultOn "click"
        (Decode.succeed ( msg, True ))

Example

See the example/ directory for a working demo that exercises several of these navigation patterns.

Nav.Event

The Nav.Event type contains:

  • appUrl : AppUrl — the parsed URL (path, query parameters, fragment)
  • state : Decode.Value — the history state object, or null if none was set

The decoder fails if location.href is not a valid URL, which should never happen in normal browser navigation.