Skip to content

Commit dcf0c60

Browse files
committed
docs: revamp readme and add docs guides
1 parent f8dee5f commit dcf0c60

6 files changed

Lines changed: 258 additions & 150 deletions

File tree

README.md

Lines changed: 29 additions & 150 deletions
Original file line numberDiff line numberDiff line change
@@ -1,168 +1,47 @@
1-
![Coverage Badge](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/LbP22/7a0933f8cba0bddbcc95c8b850e32663/raw/spa-to-http_units_passing__heads_main.json) ![Coverage Badge](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/LbP22/7a0933f8cba0bddbcc95c8b850e32663/raw/spa-to-http_units_coverage__heads_main.json)
1+
![Coverage Badge](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/LbP22/7a0933f8cba0bddbcc95c8b850e32663/raw/spa-to-http_units_passing__heads_main.json) ![Coverage Badge](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/LbP22/7a0933f8cba0bddbcc95c8b850e32663/raw/spa-to-http_units_coverage__heads_main.json)
22

33
<a href="https://devforth.io"><img src="https://raw.githubusercontent.com/devforth/OnLogs/e97944fffc24fec0ce2347b205c9bda3be8de5c5/.assets/df_powered_by.svg" style="height:36px"/></a>
44

5-
# World's fastest lightweight zero-configuration SPA HTTP server.
5+
# spa-to-http
66

7-
Simply serves SPA bundle on HTTP port which makes it play well with Traefik / Cloudflare another reverse proxy.
7+
> World's fastest lightweight zero-configuration SPA HTTP server.
88
9-
## Benefits
9+
Serve a built SPA bundle over HTTP with sensible defaults for caching and optional Brotli/Gzip compression. It’s designed to run cleanly in Docker and behind reverse proxies like Traefik or Cloudflare.
1010

11-
* Zero-configuration in Docker without managing additional configs
12-
* 10x times smaller then Nginx, faster startup time, a bit better or same performance
13-
* Plays well with all popular SPA frameworks and libraries: Vue, React, Angular, Svelte and bundlers: Webpack/Vite
14-
* Supports Brotly compression on original files, you don't need to archivate files by yourself, it does it for you
15-
* Written in Go, which makes it fast (no overhead on runtime) and tiny (small binary size)
16-
* Open-Source commercial friendly MIT license
17-
* Optimal statics caching out of the box: no-cache on index.html file to auto-update caches and infinite max-age for all other resources which have hash-URLs in most default SPA bundlers.
18-
* Plays well with CDNs caching (e.g. Clouflare), support for ignoring cache of fixed URLs like service worker
19-
* Created and maintained by Devforth 💪🏼
11+
## Quick Start
2012

21-
## Spa-to-http vs Nginx
22-
23-
| | Spa-to-http | Nginx |
24-
|---|---|---|
25-
| Zero-configuration | ✅No config files, SPA serving works out of the box with most optimal settings | ❌Need to create a dedicated config file |
26-
| Ability to config settings like host, port, compression using Environment variables or CLI | ✅Yes | ❌No, only text config file |
27-
| Docker image size | ✅13.2 MiB (v1.0.3) | ❌142 MiB (v1.23.1) |
28-
| Brotli compression out-of-the-box | ✅Yes, just set env BROTLI=true | ❌You need a dedicated module like ngx_brotli |
29-
30-
Performence accroding to [Spa-to-http vs Nginx benchmark (End of the post)](https://devforth.io/blog/deploy-react-vue-angular-in-docker-simply-and-efficiently-using-spa-to-http-and-traefik/)
31-
32-
| | Spa-to-http | Nginx |
33-
|---|---|---|
34-
| Average time from container start to HTTP port availability (100 startups) | ✅1.358 s (11.5% faster) | ❌1.514s |
35-
| Requests-per-second on 0.5 KiB HTML file at localhost * | ✅80497 (1.6% faster) | ❌79214 |
36-
| Transfer speed on 0.5 KiB HTML file * | ❌74.16 MiB/sec | ✅75.09 MiB/sec (1.3% faster) |
37-
| Requests-per-second on 5 KiB JS file at localhost * | ✅66126 (5.2% faster) | ❌62831 |
38-
| Transfer speed on 5 KiB HTML file * | ✅301.32 MiB/sec (4.5% faster) | ❌288.4 |
39-
40-
## Hello world & ussage
41-
42-
Create `Dockerfile` in yoru SPA directory (near `package.json`):
43-
44-
```
45-
FROM node:20-alpine as builder
46-
WORKDIR /code/
47-
ADD package-lock.json .
48-
ADD package.json .
49-
RUN npm ci
50-
ADD . .
51-
RUN npm run build
52-
53-
FROM devforth/spa-to-http:latest
54-
COPY --from=builder /code/dist/ .
55-
```
56-
57-
Test it locally:
58-
59-
```sh
60-
docker build -q . | xargs docker run --rm -p 8080:8080
61-
```
62-
63-
So we built our frontend and included it into container based on Spa-to-http. This way gives us great benefits:
64-
65-
* We build frontend in docker build time and improve build time for most changes (npm ci is not getting rebuild if there is no new packages)
66-
* Bundle has only small resulting dist folder, there is no source code and node_modules so countainer is small
67-
* When you start this container it serves SPA on HTTP port automatically with best settings. Spa-to-http already has right CMD inside which runs SPA-to-HTTP webserver with right caching
68-
69-
70-
# Example serving SPA with Traefik and Docker-Compose
71-
72-
```
73-
version: "3.3"
74-
75-
services:
76-
77-
traefik:
78-
image: "traefik:v2.7"
79-
command:
80-
- "--providers.docker=true"
81-
- "--providers.docker.exposedbydefault=false"
82-
- "--entrypoints.web.address=:80"
83-
ports:
84-
- "80:80"
85-
volumes:
86-
- "/var/run/docker.sock:/var/run/docker.sock:ro"
87-
88-
trfk-vue:
89-
build: "spa" # name of the folder where Dockerfile is located
90-
labels:
91-
- "traefik.enable=true"
92-
- "traefik.http.routers.trfk-vue.rule=Host(`trfk-vue.localhost`)"
93-
- "traefik.http.services.trfk-vue.loadbalancer.server.port=8080" # port inside of trfk-vue which should be used
94-
```
95-
96-
How to enable Brotli compression:
97-
98-
```diff
99-
trfk-vue:
100-
build: "spa"
101-
++ command: --brotli
102-
labels:
103-
- "traefik.enable=true"
104-
- "traefik.http.routers.trfk-vue.rule=Host(`trfk-vue.localhost`)"
105-
- "traefik.http.services.trfk-vue.loadbalancer.server.port=8080"
106-
```
107-
How to change thresshold of small files which should not be compressed:
108-
109-
```diff
110-
trfk-vue:
111-
build: "spa"
112-
-- command: --brotli
113-
++ command: --brotli --threshold 500
114-
labels:
115-
- "traefik.enable=true"
116-
- "traefik.http.routers.trfk-vue.rule=Host(`trfk-vue.localhost`)"
117-
- "traefik.http.services.trfk-vue.loadbalancer.server.port=8080"
118-
```
119-
120-
How to run container on a custom port:
121-
122-
123-
```diff
124-
trfk-vue:
125-
build: "spa"
126-
++ command: --brotli --port 8082
127-
labels:
128-
- "traefik.enable=true"
129-
- "traefik.http.routers.trfk-vue.rule=Host(`trfk-vue.localhost`)"
130-
-- - "traefik.http.services.trfk-vue.loadbalancer.server.port=8080"
131-
++ - "traefik.http.services.trfk-vue.loadbalancer.server.port=8082"
13+
```bash
14+
# Serve ./dist at http://localhost:8080
15+
docker run --rm -p 8080:8080 -v $(pwd)/dist:/code devforth/spa-to-http:latest
13216
```
13317

134-
Ignore caching for some specific resources, e.g. prevent Service Worker caching on CDNs like Cloudflare:
18+
## Key Features
13519

20+
- Zero-configuration Docker setup for common SPA outputs
21+
- Small image and fast startup (Go binary)
22+
- Optional Brotli/Gzip compression
23+
- Cache-control optimized for hashed assets and index.html
24+
- Works with popular SPA toolchains (React, Vue, Angular, Svelte, Vite, Webpack)
13625

26+
## Example
13727

138-
```diff
139-
trfk-vue:
140-
build: "spa"
141-
++ command: --ignore-cache-control-paths "/sw.js"
142-
labels:
143-
- "traefik.enable=true"
144-
- "traefik.http.routers.trfk-vue.rule=Host(`trfk-vue.localhost`)"
145-
- "traefik.http.services.trfk-vue.loadbalancer.server.port=8080"
28+
```bash
29+
# Enable Brotli and serve on a custom port
30+
docker run --rm -p 8082:8082 -v $(pwd)/dist:/code devforth/spa-to-http:latest --brotli --port 8082
14631
```
14732

148-
This is not needed for most of your assets because their filenames should contain file hash (added by default by modern bundlers). So cache naturally invalidated by referencing hashed assets from uncachable html. However some special resources like service worker must be served on fixed URL without file hash in filename
33+
---
14934

35+
## Documentation
15036

37+
| Guide | Description |
38+
|-------|-------------|
39+
| [Getting Started](docs/getting-started.md) | Install, build, and run |
40+
| [Configuration](docs/configuration.md) | Environment variables and CLI flags |
41+
| [Deployment](docs/deployment.md) | Docker and reverse proxy setup |
42+
| [Architecture](docs/architecture.md) | Project structure and request flow |
43+
| [Benchmarks](docs/benchmarks.md) | spa-to-http vs Nginx |
15144

152-
## Available Options:
45+
## License
15346

154-
| Environment Variable | Command | Description | Defaults |
155-
|----------------------------|-----------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|
156-
| ADDRESS | `-a` or `--address` | Address to use | 0.0.0.0 |
157-
| PORT | `-p` or `--port` | Port to listen on | 8080 |
158-
| GZIP | `--gzip` | When enabled it will create .gz files using gzip compression for files which size exceedes threshold and serve it instead of original one if client accepts gzip encoding. If brotli also enabled it will try to serve brotli first | `false` |
159-
| BROTLI | `--brotli` | When enabled it will create .br files using brotli compression for files which size exceedes threshold and serve it instead of original one if client accepts brotli encoding. If gzip also enabled it will try to serve brotli first | `false` |
160-
| THRESHOLD | `--threshold <number>` | Threshold in bytes for gzip and brotli compressions | 1024 |
161-
| DIRECTORY | `-d <string>` or `--directory <string>` | Directory to serve | `.` |
162-
| CACHE_MAX_AGE | `--cache-max-age <number>` | Set cache time (in seconds) for cache-control max-age header To disable cache set to -1. `.html` files are not being cached | 604800 |
163-
| IGNORE_CACHE_CONTROL_PATHS | `--ignore-cache-control-paths <string>` | Additional paths to set "Cache-control: no-store" via comma, example "/file1.js,/file2.js" | |
164-
| SPA_MODE | `--spa` or `--spa <bool>` | When SPA mode if file for requested path does not exists server returns index.html from root of serving directory. SPA mode and directory listing cannot be enabled at the same time | `true` |
165-
| CACHE | `--cache` | When enabled f.Open reads are being cached using Two Queue LRU Cache in bits | `true` |
166-
| CACHE_BUFFER | `--cache-buffer <number>` | Specifies the maximum size of LRU cache in bytes | `51200` |
167-
| LOGGER | `--logger` | Enable requests logger | `false` |
168-
| LOG_PRETTY | `--log-pretty` | Print log messages in a pretty format instead of default JSON format | `false` |
47+
MIT

docs/architecture.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
[← Deployment](deployment.md) · [Back to README](../README.md) · [Benchmarks →](benchmarks.md)
2+
3+
# Architecture
4+
5+
spa-to-http is a single Go binary focused on static file serving for SPA bundles. It keeps the runtime simple, with a small number of packages and minimal configuration.
6+
7+
For detailed architectural guidance and dependency rules, see `.ai-factory/ARCHITECTURE.md`.
8+
9+
## Project Structure
10+
11+
```
12+
./src
13+
./src/main.go # Entry point and wiring
14+
./src/app # HTTP server and handlers
15+
./src/param # CLI and environment parsing
16+
./src/util # Small shared helpers
17+
```
18+
19+
## Request Flow
20+
21+
1. Parse configuration (CLI and environment variables).
22+
2. Initialize the HTTP server and middleware.
23+
3. Serve static files from the configured directory.
24+
4. Apply caching headers optimized for SPA assets.
25+
5. Optionally serve compressed files (Brotli/Gzip) when enabled.
26+
27+
## Design Goals
28+
29+
- Fast startup and low memory footprint
30+
- Predictable caching for hashed assets and `index.html`
31+
- Simple configuration for containerized use
32+
33+
## See Also
34+
35+
- [Configuration](configuration.md) — Environment variables and CLI flags
36+
- [Getting Started](getting-started.md) — Install, build, and run
37+
- [Benchmarks](benchmarks.md) — spa-to-http vs Nginx

docs/benchmarks.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
[← Architecture](architecture.md) · [Back to README](../README.md)
2+
3+
# Benchmarks
4+
5+
## spa-to-http vs Nginx
6+
7+
| | spa-to-http | Nginx |
8+
|---|---|---|
9+
| Zero-configuration | ✅ No config files, SPA serving works out of the box | ❌ Requires dedicated config file |
10+
| Config via env/CLI | ✅ Yes | ❌ No |
11+
| Docker image size | ✅ 13.2 MiB (v1.0.3) | ❌ 142 MiB (v1.23.1) |
12+
| Brotli out-of-the-box | ✅ Yes | ❌ Requires module |
13+
14+
Performance numbers from the benchmark section of this post:
15+
`https://devforth.io/blog/deploy-react-vue-angular-in-docker-simply-and-efficiently-using-spa-to-http-and-traefik/`
16+
17+
| | spa-to-http | Nginx |
18+
|---|---|---|
19+
| Average time from container start to HTTP port availability (100 startups) | ✅ 1.358 s (11.5% faster) | ❌ 1.514 s |
20+
| Requests-per-second on 0.5 KiB HTML file at localhost | ✅ 80497 (1.6% faster) | ❌ 79214 |
21+
| Transfer speed on 0.5 KiB HTML file at localhost | ❌ 74.16 MiB/sec | ✅ 75.09 MiB/sec (1.3% faster) |
22+
| Requests-per-second on 5 KiB JS file at localhost | ✅ 66126 (5.2% faster) | ❌ 62831 |
23+
| Transfer speed on 5 KiB HTML file at localhost | ✅ 301.32 MiB/sec (4.5% faster) | ❌ 288.4 |
24+
25+
## See Also
26+
27+
- [Getting Started](getting-started.md) — Install, build, and run
28+
- [Deployment](deployment.md) — Docker and reverse proxy setup
29+
- [Configuration](configuration.md) — Environment variables and CLI flags

docs/configuration.md

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
[← Getting Started](getting-started.md) · [Back to README](../README.md) · [Deployment →](deployment.md)
2+
3+
# Configuration
4+
5+
spa-to-http supports both environment variables and CLI flags. Environment variables map directly to the CLI options.
6+
7+
## Options
8+
9+
| Environment Variable | Command | Description | Default |
10+
|---|---|---|---|
11+
| ADDRESS | `-a` or `--address` | Address to use | `0.0.0.0` |
12+
| PORT | `-p` or `--port` | Port to listen on | `8080` |
13+
| GZIP | `--gzip` | Enable gzip compression for files above the threshold | `false` |
14+
| BROTLI | `--brotli` | Enable Brotli compression for files above the threshold | `false` |
15+
| THRESHOLD | `--threshold <number>` | Threshold in bytes for gzip and Brotli | `1024` |
16+
| DIRECTORY | `-d <string>` or `--directory <string>` | Directory to serve | `.` |
17+
| CACHE_MAX_AGE | `--cache-max-age <number>` | Cache max-age in seconds; use `-1` to disable | `604800` |
18+
| IGNORE_CACHE_CONTROL_PATHS | `--ignore-cache-control-paths <string>` | Comma-separated paths to force `Cache-Control: no-store` | (empty) |
19+
| SPA_MODE | `--spa` or `--spa <bool>` | Serve `index.html` on missing paths (SPA routing) | `true` |
20+
| CACHE | `--cache` | Enable in-memory file read cache (LRU) | `true` |
21+
| CACHE_BUFFER | `--cache-buffer <number>` | Max size of the LRU cache in bytes | `51200` |
22+
| LOGGER | `--logger` | Enable request logging | `false` |
23+
| LOG_PRETTY | `--log-pretty` | Pretty-print logs instead of JSON | `false` |
24+
25+
## Examples
26+
27+
Enable Brotli:
28+
29+
```yaml
30+
# docker-compose.yml
31+
services:
32+
spa:
33+
image: devforth/spa-to-http:latest
34+
command: --brotli
35+
```
36+
37+
Change compression threshold:
38+
39+
```yaml
40+
services:
41+
spa:
42+
image: devforth/spa-to-http:latest
43+
command: --brotli --threshold 500
44+
```
45+
46+
Serve on a custom port:
47+
48+
```yaml
49+
services:
50+
spa:
51+
image: devforth/spa-to-http:latest
52+
command: --port 8082
53+
ports:
54+
- "8082:8082"
55+
```
56+
57+
Ignore cache-control for fixed paths (for example, a service worker):
58+
59+
```yaml
60+
services:
61+
spa:
62+
image: devforth/spa-to-http:latest
63+
command: --ignore-cache-control-paths "/sw.js"
64+
```
65+
66+
## See Also
67+
68+
- [Getting Started](getting-started.md) — Install, build, and run
69+
- [Deployment](deployment.md) — Docker and reverse proxy setup
70+
- [Architecture](architecture.md) — Project structure and request flow

docs/deployment.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
[← Configuration](configuration.md) · [Back to README](../README.md) · [Architecture →](architecture.md)
2+
3+
# Deployment
4+
5+
spa-to-http is designed to run as a small container and sit behind a reverse proxy. It works well with Traefik and CDNs like Cloudflare.
6+
7+
## Traefik + Docker Compose Example
8+
9+
```yaml
10+
version: "3.3"
11+
12+
services:
13+
traefik:
14+
image: "traefik:v2.7"
15+
command:
16+
- "--providers.docker=true"
17+
- "--providers.docker.exposedbydefault=false"
18+
- "--entrypoints.web.address=:80"
19+
ports:
20+
- "80:80"
21+
volumes:
22+
- "/var/run/docker.sock:/var/run/docker.sock:ro"
23+
24+
spa:
25+
image: devforth/spa-to-http:latest
26+
labels:
27+
- "traefik.enable=true"
28+
- "traefik.http.routers.spa.rule=Host(`spa.localhost`)"
29+
- "traefik.http.services.spa.loadbalancer.server.port=8080"
30+
```
31+
32+
## Notes
33+
34+
- Use `--port` if you run the container on a non-default port.
35+
- Enable compression (`--brotli` or `--gzip`) when serving large static bundles.
36+
- For fixed asset paths (for example, a service worker), use `--ignore-cache-control-paths` to avoid CDN caching issues.
37+
38+
## See Also
39+
40+
- [Configuration](configuration.md) — Environment variables and CLI flags
41+
- [Getting Started](getting-started.md) — Install, build, and run
42+
- [Benchmarks](benchmarks.md) — spa-to-http vs Nginx

0 commit comments

Comments
 (0)