servexy
| Install | |
|---|---|
npm install servexy |
|
| Latest Version: | 1.4.0 |
| License: | MIT |
| Last Updated: | Oct 17, 2026 |
| Links: | GitHub · npm |
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.
1. Install itbun add -g servexyor npm i -g servexy. Or download one file.
|
2. Point it at a folderservexy ./dist --spaFlags go before or after the folder. |
3. Open your browserhttp://localhost:8080That's the whole setup. |
Everything a file server should do
SPA routing that tells the truthRefresh/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 defaultListens on127.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 invitedAdd--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 countsA compiled Go server with on-the-fly gzip for text, and zero-copysendfile transfers
for images, video and other binaries. It's ready to serve in milliseconds.
|
Caching done rightHTML 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 nothingOne self-contained binary for macOS, Linux and Windows on x64 and ARM64. The npm package has no dependencies and no install scripts. |
Contents
- Requirements
- Install
- Usage
- Recipes
- SPA routing
- Safe by default
- How responses are served
- What servexy is not
- Troubleshooting
- Build from source
- License
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
-
Download the file for your system from the latest release:
System File macOS, Apple silicon servexy-darwin-arm64macOS, Intel servexy-darwin-amd64Linux, x64 servexy-linux-amd64Linux, ARM64 servexy-linux-arm64Windows, x64 servexy-windows-amd64.exeWindows, ARM64 servexy-windows-arm64.exe -
Rename it to
servexy(servexy.exeon Windows) and move it to a folder on yourPATH. On macOS and Linux, make it executable too:chmod +x servexy sudo mv servexy /usr/local/bin/ -
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 3000or--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,-spaand-networkfrom 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:
- If the path is a file, or a folder with its own
index.html, that is served. - Otherwise, if the request is a page navigation, the root
index.htmlis served with200 OK. - 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-Modeisnavigate, or whenSec-Fetch-Destisdocument,iframeorframe. When these headers are present, they decide on their own, sofetch(), images, scripts and styles never get the app. - Otherwise, a request whose
Acceptheader includestext/htmlorapplication/xhtml+xmlcounts as a navigation. - Tools like curl send
Accept: */*or noAcceptat all. Those get the app only when the path has no file extension, socurl localhost:8080/settingsreturns your app andcurl localhost:8080/missing.jsreturns404.
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 soservexy listens on127.0.0.1. Nothing else on the network can connect until you pass
--network, which listens on every interface, IPv6 included.
|
Locked to one folderFiles are opened through Go'sos.Root, which
refuses any path or symlink that leads outside the folder. Symlinks that stay inside it work as usual.
|
Hidden files stay hiddenAny 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-onlyOnlyGET 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
sendfilewhere the system supports it. HEADand 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 anindex.htmlshows 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 devand 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
--networkand 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, notlocalhost. - 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--dotfilesif 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.
-
Set the new
versioninpackage.jsonand commit it. -
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.
Related Packages
A Vite plugin that adds source location and component information to DOM element...
A Vite plugin that adds source location and component information to DOM element...
Vite plugin to enable SPA (Single Page Application) support for GitHub Pages
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 |