servexy

Fast production-ready static file server with reliable SPA routing.
699 6
Install
npm install servexy
Latest Version:1.4.0
License:MIT
Last Updated:Oct 17, 2026
Links: GitHub  ·  npm
Maintainer: rahatsagor

servexy icon

servexy

Your files. Now serving.

A tiny, zero-config static file server for previewing builds, testing SPA routes,
and sharing a folder with the devices on your network.

One command. One native binary. Nothing to configure.

npm version Website

Latest release CI status macOS, Linux and Windows on x64 and ARM64 Go version MIT License

A terminal runs servexy ./dist --spa --network. It reports the folder being served, that SPA fallback and gzip are on, and prints a local and a network URL.

1. Install it
bun add -g servexy
or npm i -g servexy. Or download one file.
2. Point it at a folder
servexy ./dist --spa
Flags go before or after the folder.
3. Open your browser
http://localhost:8080
That's the whole setup.

Everything a file server should do

SPA routing that tells the truth
Refresh /dashboard/settings or /users/jane.doe and your app loads. A missing script, a typo in an image path or a fetch() to an API that doesn't exist still gets a real 404, so broken paths never quietly turn into HTML.
Safe by default
Listens on 127.0.0.1 only. .env, .git/ and every other hidden path return 404. Symlinks can't reach outside the folder you serve, and only GET and HEAD are allowed.
Your phone is invited
Add --network and servexy prints a LAN address like http://192.168.1.42:8080 you can open on any phone, tablet or laptop on the same Wi-Fi.
Fast where it counts
A compiled Go server with on-the-fly gzip for text, and zero-copy sendfile transfers for images, video and other binaries. It's ready to serve in milliseconds.
Caching done right
HTML is always revalidated, so a new build shows up on the next refresh. Add --cache 24h to let browsers keep your hashed assets.
Runs anywhere, installs nothing
One self-contained binary for macOS, Linux and Windows on x64 and ARM64. The npm package has no dependencies and no install scripts.

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
    
Verify the download

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

With --spa, navigations to /dashboard/settings and /users/jane.doe get index.html. An existing asset is served as itself. A missing script, a fetch() to /api/users and /.env get 404.

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.
How servexy recognizes a page navigation
  • 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

Only your computer, unless you say so
servexy listens on 127.0.0.1. Nothing else on the network can connect until you pass --network, which listens on every interface, IPv6 included.
Locked to one folder
Files are opened through Go's os.Root, which refuses any path or symlink that leads outside the folder. Symlinks that stay inside it work as usual.
Hidden files stay hidden
Any path with a part that starts with a dot, like .env, .git/config or public/.htpasswd, returns 404 and is left out of file listings, in SPA mode too. .well-known/ is allowed, but hidden files inside it are not.
Read-only
Only GET and HEAD are accepted. There is no upload, no delete and nothing that runs code from the folder. Every response carries X-Content-Type-Options: nosniff.

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

Compression
  • 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.
Caching
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.

Connections, limits and shutdown
  • 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

port 8080 is already in use

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

permission denied for port 80

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 mode requires index.html

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

A deep link shows 404 after a refresh

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

My phone can't open the network address
  • 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.
A file exists but returns 404
  • 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.
binary not found from the npm package

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.
Project layout
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.
Releasing

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.


servexy
servexy.rsapplication.com

Related Packages

agod-cli

🚀 agod前端脚手架

2,032
vite-plugin-source-identifier

A Vite plugin that adds source location and component information to DOM element...

797,687
vite-plugin-source-info

A Vite plugin that adds source location and component information to DOM element...

14,961
@moluoxixi/create-app

Create supported Vite projects with an atomic layered architecture

4,573 0
@sctg/vite-plugin-github-pages-spa

Vite plugin to enable SPA (Single Page Application) support for GitHub Pages

4,991 17

Version History

Version Released License
1.4.0 MIT
1.3.0 MIT
1.2.0 MIT
1.1.0 MIT
1.0.0 MIT