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

@lorb/phantom-page

v0.2.0

Published

Click a link. The page materializes. It never existed before that moment.

Readme

@lorb/phantom-page

Pages that don't exist until you click them.

No server. Routes live in the browser. A Service Worker intercepts navigation and generates HTML on the fly — styled, dynamic, instant.

No files. No static HTML to maintain. Define routes in JavaScript, and pages materialize at the moment of navigation.

No build step. Works with any framework or vanilla JS. Drop it in and links start working.

import { init } from '@lorb/phantom-page';

init({
  routes: {
    '/hello': () => `<html><body><h1>Hi.</h1></body></html>`,
  },
});
// Click a link to /hello → the page appears. It never existed before.

Install

npm install @lorb/phantom-page

What you can do

Style pages with CSS

Handlers return full HTML — include any CSS you want.

init({
  routes: {
    '/about': () => `
      <html>
      <head>
        <style>
          body { font-family: system-ui; background: #0a0a0a; color: #fafafa; }
          h1 { font-size: 4rem; font-weight: 200; letter-spacing: -0.02em; }
          .container { max-width: 640px; margin: 0 auto; padding: 4rem 2rem; }
        </style>
      </head>
      <body>
        <div class="container">
          <h1>About</h1>
          <p>This page was generated at ${new Date().toLocaleTimeString()}.</p>
        </div>
      </body>
      </html>
    `,
  },
});

Dynamic pages with route params

:param captures URL segments. Use them to generate content.

init({
  routes: {
    '/user/:id': ({ id }) => `
      <html><body>
        <h1>Profile #${id}</h1>
        <p>Generated on the fly — no database, no server.</p>
      </body></html>
    `,

    '/post/:slug': ({ slug }) => `
      <html><body>
        <h1>${slug.replace(/-/g, ' ')}</h1>
      </body></html>
    `,
  },
});

Catch-all routes with wildcards

init({
  routes: {
    '/docs/*': () => `
      <html><body>
        <h1>Documentation</h1>
        <p>Every /docs/* URL resolves to this page.</p>
      </body></html>
    `,
  },
});

Serve JSON or any content type

Return a Response object for full control over headers and status codes.

init({
  routes: {
    '/api/time': () => new Response(
      JSON.stringify({ time: Date.now() }),
      { headers: { 'Content-Type': 'application/json' } },
    ),
    '/api/redirect': () => new Response(null, {
      status: 302,
      headers: { Location: '/hello' },
    }),
  },
});

Wait for readiness before navigating

The Service Worker needs a moment to activate. Use waitUntilReady() if you navigate programmatically.

import { init, waitUntilReady } from '@lorb/phantom-page';

init({ routes: { /* ... */ } });
await waitUntilReady();
window.location.href = '/hello'; // Safe — route is active

Browser compatibility

By default, phantom-page creates a Service Worker from a blob URL. This works in Chrome and Edge but throws SecurityError in Firefox and Safari.

For cross-browser support, use the swUrl option to register a real Service Worker file:

import { init, generateSWScript } from '@lorb/phantom-page';

// 1. Generate the SW script at build time and write it to a file:
const script = generateSWScript(
  { '/hello': () => `<html><body><h1>Hi.</h1></body></html>` },
);
// Write `script` to your public directory as `phantom-sw.js`

// 2. Point init() to the file:
init({
  routes: { '/hello': () => `<html><body><h1>Hi.</h1></body></html>` },
  swUrl: '/phantom-sw.js',
});

You can also check support at runtime:

import { init, isSupported } from '@lorb/phantom-page';

if (isSupported()) {
  // Blob URL approach — Chrome/Edge only
  init({ routes: { /* ... */ } });
} else {
  // Fall back to swUrl approach
  init({ routes: { /* ... */ }, swUrl: '/phantom-sw.js' });
}

| Browser | Default (blob URL) | With swUrl | |---------|-------------------|-------------| | Chrome / Edge | Full support | Full support | | Firefox | Not supported | Full support | | Safari | Not supported | Full support |

Constraints

  • Handlers must be self-contained. They run inside a Service Worker via toString() serialization — closures and external imports won't work. phantom-page validates this at init and throws if a handler references outside scope.
  • Same-origin only (Service Worker limitation).
  • SSR-safe — no-op without navigator.serviceWorker.

API

| Export | Description | |--------|-------------| | init(config) | Register routes. Returns async destroy() function | | waitUntilReady() | Resolves when the Service Worker is active and controlling the page | | destroy() | Unregister the Service Worker | | isSupported() | Check if blob URL SW registration works in this browser | | generateSWScript(routes, fallback?) | Generate the Service Worker script string (for the swUrl approach) |

Config options:

| Option | Type | Description | |--------|------|-------------| | routes | Record<string, RouteHandler> | Route pattern to handler map | | fallback | string | URL to redirect to on handler errors | | swUrl | string | Path to a pre-built SW file (required for Firefox/Safari) |

Route patterns: /path, /path/:param, /path/*

License

𖦹 MIT — Lorb.studio