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

servexy

v1.4.0

Published

Fast production-ready static file server with reliable SPA routing.

Readme

Everything a file server should do

Contents

Requirements

  • macOS, Linux or Windows, on x64 or ARM64.
  • For the npm package: Node.js 18 or later, or Bun. The prebuilt binaries need nothing else.

Install

With Bun or npm

bun add --global servexy
# or
npm install --global servexy

Run the same commands again to update. To try it without installing:

bunx servexy ./dist --spa
# or
npx servexy ./dist --spa

The package ships a small launcher plus a native binary for every supported platform. The launcher picks the right one, so there is no download step during install and nothing runs at install time.

Or download the binary

  1. Download the file for your system from the latest release:

    | System | File | | ---------------------- | --------------------------- | | macOS, Apple silicon | servexy-darwin-arm64 | | macOS, Intel | servexy-darwin-amd64 | | Linux, x64 | servexy-linux-amd64 | | Linux, ARM64 | servexy-linux-arm64 | | Windows, x64 | servexy-windows-amd64.exe | | Windows, ARM64 | servexy-windows-arm64.exe |

  2. Rename it to servexy (servexy.exe on Windows) and move it to a folder on your PATH. On macOS and Linux, make it executable too:

    chmod +x servexy
    sudo mv servexy /usr/local/bin/
  3. On macOS, a binary downloaded with a browser is quarantined. Clear the flag once:

    xattr -d com.apple.quarantine /usr/local/bin/servexy

Every release includes a SHA256SUMS file. Put it next to the binary and run:

shasum -a 256 --check --ignore-missing SHA256SUMS   # macOS
sha256sum --check --ignore-missing SHA256SUMS       # Linux

On Windows, compare the output of Get-FileHash servexy-windows-amd64.exe with the matching line in SHA256SUMS.

npm releases are published from GitHub Actions with provenance, so npmjs.com shows the exact commit and workflow that built each version.

Or install with Go

go install github.com/rahatsagor/servexy@latest

This needs Go 1.27 or later. Builds made this way report their version as dev.

Usage

servexy [OPTIONS] [DIRECTORY]
servexy                          # serve the current folder on http://localhost:8080
servexy ./public --port 3000     # another folder and port
servexy ./dist --spa             # a single-page app with client-side routing
servexy ./dist --spa --cache 1h  # also let browsers cache assets for an hour
servexy ./public --network       # reachable from other devices on your network

| Option | Default | What it does | | ----------------------- | -------------- | ----------------------------------------------------------------------------- | | DIRECTORY | current folder | The folder to serve. Give it here or with --dir, not both. | | -p, --port <port> | 8080 | Port to listen on, from 1 to 65535. | | -d, --dir <directory> | current folder | The folder to serve. | | -s, --spa | off | Serve the root index.html for page navigations that don't match a file. | | -n, --network | off | Listen on every network interface instead of only 127.0.0.1. | | --cache <duration> | off | Let browsers cache files other than HTML, for example 10m, 1h or 24h. | | --no-compress | off | Turn off gzip compression. | | --dotfiles | off | Serve hidden files and folders such as .env. | | -v, --version | | Print the version. | | -h, --help | | Print help. |

  • Options can come before or after the folder, and values can be written as --port 3000 or --port=3000.
  • -- ends the options, which lets you serve a folder whose name starts with a dash: servexy -- -drafts.
  • The single-dash forms -port, -dir, -spa and -network from older versions still work.

Press Ctrl+C to stop. Requests that are still running get up to five seconds to finish; press Ctrl+C again to quit at once.

Recipes

Preview a production build

| Built with | Output folder | Command | | ---------------------------------- | --------------------- | ------------------------------------- | | Vite (Vue, React, Svelte, Solid) | dist/ | servexy ./dist --spa | | Create React App | build/ | servexy ./build --spa | | Angular 17 or later | dist/<app>/browser/ | servexy ./dist/my-app/browser --spa | | Next.js with output: 'export' | out/ | servexy ./out | | Astro (static output) | dist/ | servexy ./dist | | Hugo | public/ | servexy ./public | | Eleventy | _site/ | servexy ./_site |

Vue Router's history mode, React Router's createBrowserRouter, and any other router that uses real URLs need --spa so a refresh on a deep link works. Hash routing (/#/settings) works without it.

To pin it to your project, add it as a dev dependency with bun add --dev servexy (or npm install --save-dev servexy) and use it in a script:

{
  "scripts": {
    "build": "vite build",
    "preview": "servexy ./dist --spa"
  }
}

Test on your phone

servexy ./dist --spa --network

Open the network: address servexy prints on any device on the same Wi-Fi. If nothing loads, check that your firewall allows incoming connections for servexy. Everyone on that network can reach the folder while it runs, so only share what you mean to.

Run end-to-end tests against the real build

Point Playwright at your build output, so tests exercise exactly what you ship:

// playwright.config.ts
import { defineConfig } from '@playwright/test';

export default defineConfig({
  webServer: {
    command: 'bunx servexy ./dist --spa --port 4173',
    url: 'http://127.0.0.1:4173',
    reuseExistingServer: !process.env.CI,
  },
  use: { baseURL: 'http://127.0.0.1:4173' },
});

Because missing assets and fetch() calls stay 404, a broken import or API path fails the test instead of hiding behind your index.html.

Cache hashed assets

servexy ./dist --spa --cache 24h

HTML keeps Cache-Control: no-cache, so a new deploy shows up on the next refresh. Everything else gets public, max-age=86400. Use this when your bundler puts a content hash in file names, like assets/index-3f9a1c.js. Without hashes, browsers may keep an old file until the cache runs out.

SPA routing

Most SPA servers send index.html for every path they can't find. That keeps refreshes working, but it also answers a missing app.js or a mistyped API call with 200 OK and a page of HTML, which turns a clear 404 into a confusing parse error. servexy only sends your app to requests that are actually page loads.

With --spa, each request is answered like this:

  1. If the path is a file, or a folder with its own index.html, that is served.
  2. Otherwise, if the request is a page navigation, the root index.html is served with 200 OK.
  3. Anything else gets 404 Not Found.
  • Modern browsers send Fetch Metadata headers. A request is a navigation when Sec-Fetch-Mode is navigate, or when Sec-Fetch-Dest is document, iframe or frame. When these headers are present, they decide on their own, so fetch(), images, scripts and styles never get the app.
  • Otherwise, a request whose Accept header includes text/html or application/xhtml+xml counts as a navigation.
  • Tools like curl send Accept: */* or no Accept at all. Those get the app only when the path has no file extension, so curl localhost:8080/settings returns your app and curl localhost:8080/missing.js returns 404.

That is why dotted routes like /users/jane.doe load in the browser, while /assets/app.jss stays 404. GET and HEAD are treated the same way.

SPA mode needs an index.html at the root of the folder and won't start without one. A page load of a folder that has no index.html of its own also gets the app, not a file listing.

Safe by default

Pass --dotfiles only when you really want hidden files served.

On Windows, path parts that look like 8.3 short names, such as GIT~1, are treated as hidden too, because NTFS can resolve them to .git or .env. As a result, a file whose name contains ~ followed by a digit isn't served on Windows unless you pass --dotfiles.

How responses are served

  • Responses are gzipped when the browser accepts gzip, the body is at least 1 KiB, and the content is text: HTML, CSS, JavaScript, JSON, web manifests, WebAssembly, XML or SVG.
  • Images, video, fonts and archives are already compressed, so they are sent as they are, using zero-copy sendfile where the system supports it.
  • HEAD and range requests are never compressed, and compressed responses don't offer byte ranges.
  • Every compressible response includes Vary: Accept-Encoding, so a cache in front of servexy keeps the gzipped and plain versions apart.

| Response | Cache-Control | | --------------------------------------------------------- | --------------------------------------------------------- | | HTML, paths without an extension, folders, SPA fallback | no-cache | | Every other file | no-cache, or public, max-age=<seconds> with --cache | | Errors such as 404 | none |

Files are sent with Last-Modified, so browsers revalidate with a quick 304 Not Modified instead of downloading the file again.

  • Request headers must arrive within 5 seconds and are limited to 1 MiB. Idle keep-alive connections close after 60 seconds.
  • There is no overall write timeout, so large downloads on slow connections are never cut off.
  • Without --spa, a folder without an index.html shows a simple file listing, with hidden files left out.
  • On Ctrl+C or SIGTERM, servexy stops accepting connections and waits up to 5 seconds for running requests before closing them. A second Ctrl+C exits at once.

What servexy is not

  • A dev server. It serves files that already exist. There's no bundling and no hot reload; keep using vite, next dev and friends while you write code, and use servexy to check the build.
  • An HTTPS server. It speaks plain HTTP. For HTTPS, put it behind a reverse proxy such as Caddy, or use a tunnel.
  • A full web server. There are no request logs, rewrites, headers config or virtual hosts. If you need those, nginx or Caddy are the right tools.

Troubleshooting

Another program is listening on that port, often another dev server. Stop it, or pick another port with --port 3000.

Most systems only let administrators use ports below 1024. Use a higher port, or run servexy behind a reverse proxy. Windows also reserves some port ranges; list them with:

netsh interface ipv4 show excludedportrange protocol=tcp

--spa needs to point at your build output, such as dist/ or build/, not the project folder. Run your build first.

Add --spa. Without it, servexy only serves files that exist, so /dashboard/settings is a 404.

  • Make sure you passed --network and both devices are on the same network. Guest networks and some office or hotel Wi-Fi stop devices from reaching each other.
  • Use the network: address servexy prints, not localhost.
  • Allow incoming connections for servexy in your firewall. If the macOS firewall is on, it asks the first time; on Windows, allow it for private networks.
  • Check whether its path has a hidden part, such as .cache/ or .vitepress/. Pass --dotfiles if you really mean to serve it.
  • Check whether it's a symlink that points outside the folder you're serving.
  • On Windows, check whether a part of the path looks like NAME~1.

The package manager skipped some of the package's files. Reinstall servexy. If you run it from a clone of this repository, build the binaries first with bun run build:binaries.

Still stuck? Open an issue with your operating system, the command you ran and what servexy printed.

Build from source

You need Go 1.27.2 or later, Bun 1.4.2 or later, and Node.js 18 or later for the launcher tests.

git clone https://github.com/rahatsagor/servexy.git
cd servexy
go run . ./some-folder --spa   # run from source

| Command | What it does | | ------------------------ | -------------------------------------------------------------- | | bun run check | Go tests and go vet. | | bun run test:race | Tests with the race detector. | | bun run vuln | Scan for known vulnerabilities with govulncheck. | | bun run build:current | Build the binary for this machine into dist/. | | bun run verify:current | Check that binary runs and reports the package version. | | bun run test:launcher | Test the npm launcher, including Ctrl+C handling. | | bun run build:binaries | Build all six release binaries and dist/SHA256SUMS. |

| Path | What's inside | | -------------------- | ---------------------------------------------------------------------- | | main.go | The whole server: options, file system, SPA routing and compression. | | main_test.go | Tests for routing, compression, caching, hidden files and options. | | bin/servexy | The npm launcher that starts the right native binary. | | scripts/ | Build, package verification and launcher test scripts. | | docs/media/ | Images used in this README. | | .github/workflows/ | CI on Linux, macOS and Windows, and the release workflow. |

Releases are built and published by GitHub Actions when a version tag is pushed. npm trusted publishing is set up for the rahatsagor/servexy repository and the publish.yml workflow, so no npm token is stored anywhere.

  1. Set the new version in package.json and commit it.

  2. Tag the commit and push both:

    git tag vX.Y.Z
    git push origin main vX.Y.Z

The workflow tests, vets and scans the code, builds all six binaries, checks their checksums and that the tag matches package.json, creates the GitHub release, and publishes to npm with provenance. Every workflow action is pinned to a commit SHA.

Bug reports and ideas are welcome as issues.

License

Free and open source under the MIT License. © 2026 Rahat Sagor.