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

swarf

v0.1.6

Published

Serve dynamic HTML with React inside a Service Worker

Readme

Swarf

Swarf stands for Service Worker App Router Framework.

It is a package that provides an API similar to Next.js's App Router, but designed for a Service Worker context.

Philosophy

Swarf is an opinionated framework that requires using TypeScript and TSX.

Under the hood, this package uses @express-worker/app to serve the routes. If this framework is too opinionated for your needs, consider using @express-worker/app directly. For a more fully-featured Service Worker framework, look to Google's Workbox.

Features

App Router

Similar to Next.js, routes are defined as a directory structure under src/app.

React SSR

The initial page HTML is rendered by React-DOM/Server which then gets hydrated by React-DOM/Client.

Static File Handling

Static files in the src/app directory are cached and served by the service worker.

Installation

  1. In a new repository, run npm install swarf --save

  2. Run npx swarf init to establish the essential file structure.

You can also use the template for the starter kit.

Usage

The folders inside src/app are used to define the routes. Folders can be nested. The page.tsx file inside each folder is the component that renders the body of the page.

To add a new route:

  1. Create a new folder in the src/app directory and create a page.tsx file inside it.

    Dynamic routes are defined by using square brackets in the folder name. For example, if you want to create a dynamic route for user profiles, you can create a folder with a path of src/app/profiles/[id].

  2. Define the React component for the page as the default export.

  3. Define and export getStaticProps and metadata. (See below.)

  4. Run swarf build to regenerate the routes.

Static Props

getStaticProps is an asynchronous function used to fetch data at render time. It allows you to call an API or query a database and pass the results as props to the page component. The path params are passed to this function. You can define getStaticProps as follows:

export const getStaticProps: GetStaticProps = async function ({
  params: { id },
}) {
  const apiData = await fetch(`https://myapi.example/data/${id}`).then((res) =>
    res.json(),
  );

  return {
    props: {
      apiData,
    },
  };
};

If the data is unlikely to change, you can use this function to handle caching using the browser's caches API:

export const getStaticProps: GetStaticProps = async function ({
  params: { id },
}) {
  const todosCache = await caches.open('todos-page-props');
  const cachedTodo = await todosCache.match(id);

  // Check the cache first.

  if (cachedTodo) {
    return await cachedTodo.json();
  }

  const todo = await fetchTodo({ id });

  // Save to cache.

  todosCache.put(id, new Response(JSON.stringify(todo)));

  return {
    props: {
      todo,
    },
  };
};

Using a similar strategy, images and other resources can be precached at this stage:

export const getStaticProps: GetStaticProps = async function ({
  params: { id },
}) {
  const userComment = await fetchUserComment({ id });
  const userAvatarUrl = userComment.avatarUrl;

  const userAvatarsCache = await caches.open('user-avatars');
  const cachedUserAvatar = await userAvatarsCache.match(userAvatarUrl);

  if (!cachedUserAvatar) {
    const userAvatarFileContents = await fetch(userAvatarUrl).then((res) =>
      res.blob(),
    );

    // The image will be available immediately on page load.
    userAvatarsCache.put(userAvatarUrl, new Response(userAvatarFileContents));
  }

  return {
    props: {
      userComment,
    },
  };
};

Metadata

metadata is an object that contains information about the page, such as the title and description. You can define metadata as follows:

export const metadata: Metadata = {
  title: 'Page Title',
  description: 'Page description',
};

If you need to access the route params, you can export a function instead:

export const metadata: GetMetadata = ({ params: { id } }) => ({
  title: 'Note ' + id,
});

Page Component

The default export should be the Page component. It will receive the props defined in getStaticProps. It will be wrapped in the PageShell.

export default function HomePage({ data }: { data: Data }) {
  return <main>{data.foo}</main>;
}

Development

Run swarf dev during development.

For easiest debugging, in the Web Inspector, under the Application tab, under Service Workers, select the checkbox for "Update on reload".

Production Builds

Run swarf build to generate a production build/

The built-in strategy for invalidating the old cache and serving the updated content is through incrementing the version in router.config.json.

Publish the public directory to a static file hosting service. A 404.html file should be served as a catch-all route.

Benefits

Fast Page Rendering

Routes can pre-cache or inline key assets.

Simplified Hosting

The server only needs to be able to serve the initial static assets. This project uses Firebase Static Hosting.

Offline-Ready

When a user navigates to a route while offline, the service worker can serve a previously cached response or generate a new one based on stored data.

Drawbacks

No Search Engine Indexing

Pages generated by a service worker aren't indexed by search engines. You may need alternative strategies for SEO, such as generating static HTML snapshots or using a separate build process to create server-rendered pages.

Limitations / Future Work

  • React Server Components (streaming responses) are not yet supported.
  • Decide if this could/should be a Next.js plugin.

License

This project is licensed under the MIT License.