vaserve
vaserve 是 vercel/serve 的 Rust 版本。 Static file serving and directory listing — API-compatible with all CLI arguments and configuration of the original vercel/serve.
User Story
As a developer working on static websites, single-page applications, or any project with static assets, I want a zero-configuration, instant-on static file server so that I can quickly preview my work, share it on the local network, and iterate without needing to install or configure Node.js, nginx, Apache, or any heavy web server infrastructure.
What Problems Does serve Solve?
-
"I just want to see what my build output looks like" You've run
npm run buildand have adist/folder. Openingindex.htmldirectly in the browser breaks — relative paths fail, API calls to/apireturn nothing, and routing relies on an HTTP server context.vaserve dist/gives you a fully working server in one command. -
"I need to test my SPA routing" Your React/Vue/Svelte app uses client-side routing (
/about,/dashboard). Direct file access can only serveindex.html. Withvaserve -s dist/, every not-found route rewrites toindex.html— just like production. -
"I want to share files on my local network" You have screenshots, documents, or build artifacts to share with teammates.
vaservestarts in seconds, copies the URL to your clipboard, and with-Cenables CORS so your colleagues can access everything with zero config. -
"I want to avoid Node.js dependency overhead" The original
servepulls in Node.js, npm, and hundreds of transient dependencies. You're working in a Rust ecosystem — or you simply want a single, tiny binary.vaserveis a drop-in replacement: same CLI, sameserve.jsonconfig, same behavior. Substitutenpx servewithvaserve. -
"I need a static server in CI/CD or a Docker container" A single statically-linked binary with no runtime dependencies and a tiny footprint.
vaserve dist/works anywhere Rust compiles — Linux, macOS, Windows, ARM. Deploy it in aFROM scratchDocker image, drop it into a CI pipeline, or run it on a Raspberry Pi.
Installation
From crates.io
From Source
The binary will be at target/release/vaserve. Copy it to a directory in your $PATH:
Requirements
- Rust 1.70+ (MSRV)
Quick Start
# Serve the current directory on port 3000
# Serve a specific folder
# Custom port
# SPA mode (rewrite all routes to index.html)
# With CORS
# Multiple endpoints
Usage
$ vaserve --help
$ vaserve --version
$ vaserve folder_name
$ vaserve [-l listen_uri [-l ...]] [directory]
By default, vaserve listens on 0.0.0.0:3000 and serves the current working directory.
CLI Options
| Option | Alias | Description | Default |
|---|---|---|---|
--help |
-h |
Show help message | — |
--version |
-v |
Display version | — |
--listen <uri> |
-l |
Listen URI (repeatable) | 0.0.0.0:3000 |
-p <port> |
Custom port (deprecated, use -l) |
— | |
--single |
-s |
SPA mode: rewrite all not-found to index.html |
false |
--debug |
-d |
Show debug information | false |
--config <path> |
-c |
Custom path to serve.json |
serve.json |
--no-request-logging |
-L |
Disable request logging | false |
--cors |
-C |
Enable CORS (Access-Control-Allow-Origin: *) |
false |
--no-clipboard |
-n |
Don't copy address to clipboard | false |
--no-compression |
-u |
Disable gzip compression | false |
--no-etag |
Send Last-Modified instead of ETag |
false |
|
--symlinks |
-S |
Resolve symlinks instead of 404 | false |
--ssl-cert <path> |
Path to SSL certificate (PEM/PKCS12) | — | |
--ssl-key <path> |
Path to SSL private key | — | |
--ssl-pass <path> |
Path to SSL passphrase file | — | |
--no-port-switching |
Don't auto-switch port if occupied | false |
Listen Endpoints
# Simple port (defaults to 0.0.0.0)
# TCP with host
# Host:port
# Multiple endpoints
serve.json Configuration
Create a serve.json file in your public directory to declaratively configure behavior:
Configuration Reference
| Key | Type | Description |
|---|---|---|
public |
string |
Directory path to serve |
cleanUrls |
boolean | string[] |
Strip .html//index from URLs |
trailingSlash |
boolean |
Force add/remove trailing slashes |
rewrites |
{source, destination}[] |
URL rewrite rules |
redirects |
{source, destination, type}[] |
HTTP redirect rules (default: 301) |
headers |
{source, headers[]}[] |
Custom HTTP headers per route |
directoryListing |
boolean | string[] |
Enable/disable directory listing |
unlisted |
string[] |
Paths hidden from directory listing |
renderSingle |
boolean |
SPA mode (rewrite 404s to index.html) |
symlinks |
boolean |
Follow symbolic links |
etag |
boolean |
Generate ETag headers for caching |
Rewrite Pattern Syntax
| Pattern | Example | Description |
|---|---|---|
** |
** |
Match all paths |
/prefix/** |
/api/** |
Match paths under a prefix |
* wildcard |
*.php |
Match within a single segment |
:param |
/user/:id |
Named path parameter |
Features
File Serving
- Correct
Content-Typeheaders via file extension detection Content-Disposition: inlinefor all filesAccept-Ranges: bytesfor partial content support- Automatic gzip compression (disable with
--no-compression)
ETag Caching
SHA-1 based ETags, cached per-file with mtime-based invalidation. Disable with --no-etag to use Last-Modified instead.
Range Requests
Full support for HTTP byte range requests:
Invalid ranges return 416 Range Not Satisfiable.
Directory Listing
Clean, responsive HTML directory listings matching serve's design:
- Directories listed before files, alphabetical within each group
- Breadcrumb navigation
- Folder and file icons with SVG CSS backgrounds
- JSON output when
Accept: application/jsonis set - Hidden files filtered (
.DS_Store,.git, customunlistedpatterns)
Error Pages
- 400 Bad Request — path traversal or malformed URLs
- 404 Not Found — missing files, disabled symlinks
- 500 Internal Server Error — unhandled exceptions
- HTML output (matching serve's design) or JSON (
Accept: application/json) - Custom error pages supported: place
404.htmlin your public directory
Port Switching
When a requested port is occupied, serve automatically selects an available port. Disable with --no-port-switching.
Clipboard
On start, the local URL is automatically copied to the clipboard. Disable with --no-clipboard.
SPA Mode
--single / -s enables Single Page Application mode. All not-found requests are rewritten to index.html, allowing client-side routing to work correctly.
Architecture
src/
├── main.rs # Entry point — CLI and server orchestration
├── lib.rs # Library root (exposes modules for testing)
├── cli.rs # CLI argument parsing via clap (derive)
├── config.rs # serve.json deserialization + CLI merge
├── server.rs # HTTP server, port switching, startup messages
├── handler.rs # Core request handler (serve-handler logic)
└── templates.rs # Directory listing & error page HTML
Technology Stack
| Component | Crate |
|---|---|
| HTTP server | axum 0.7 + hyper 1 |
| Async runtime | tokio |
| CLI parsing | clap 4 (derive) |
| JSON config | serde + serde_json |
| MIME types | mime_guess |
| ETags | sha1 |
| Clipboard | arboard |
| Gzip | flate2 |
Development
Build
Run
Test
The test suite includes 38 integration tests covering:
- File serving (HTML, JS, plain text, binary)
- MIME type detection
- Directory listing (HTML + JSON)
- SPA mode rewrites
- Clean URL redirects
- Range requests (byte ranges, suffix ranges)
- Error pages (HTML + JSON formats)
- Path traversal protection
- serve.json parsing (all configuration keys)
- CLI argument parsing (all flags and combinations)
- Listen URI parsing
- ETag and no-ETag modes
- Query string handling
- URL encoding/decoding
Current Limitations
- SSL/TLS: CLI flags are accepted and parsed, but HTTPS serving is not yet implemented. Use a reverse proxy (nginx, Caddy) for HTTPS.
- Compression middleware: The
--no-compressionflag is parsed but gzip middleware is not yet wired into the request pipeline. - UNIX domain sockets / Windows named pipes: Listen endpoint formats are documented but not yet implemented.
- Trailing slash redirects: The
trailingSlashconfig option is parsed but redirects are not yet active.
License
MIT