{"id":19485736,"url":"https://github.com/devforth/spa-to-http","last_synced_at":"2025-04-05T16:10:09.545Z","repository":{"id":59490213,"uuid":"498327864","full_name":"devforth/spa-to-http","owner":"devforth","description":"Lightweight zero-configuration SPA HTTP server. Serves SPA bundle on HTTP port so it plays well with Traefik out of the box. Compatible with Vue.js, React and Angular","archived":false,"fork":false,"pushed_at":"2025-03-01T21:31:34.000Z","size":159,"stargazers_count":114,"open_issues_count":4,"forks_count":9,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-03-29T15:10:01.799Z","etag":null,"topics":["brotli","http","http-server","spa","traefik"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/devforth.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-05-31T12:32:16.000Z","updated_at":"2025-03-01T21:31:37.000Z","dependencies_parsed_at":"2023-12-13T13:43:14.162Z","dependency_job_id":"a5d36934-c24d-482d-b629-3a9a8f6781fa","html_url":"https://github.com/devforth/spa-to-http","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devforth%2Fspa-to-http","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devforth%2Fspa-to-http/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devforth%2Fspa-to-http/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devforth%2Fspa-to-http/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/devforth","download_url":"https://codeload.github.com/devforth/spa-to-http/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247361693,"owners_count":20926643,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["brotli","http","http-server","spa","traefik"],"created_at":"2024-11-10T20:29:54.405Z","updated_at":"2025-04-05T16:10:09.525Z","avatar_url":"https://github.com/devforth.png","language":"Go","readme":"![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) \n\n\u003ca href=\"https://devforth.io\"\u003e\u003cimg src=\"https://raw.githubusercontent.com/devforth/OnLogs/e97944fffc24fec0ce2347b205c9bda3be8de5c5/.assets/df_powered_by.svg\" style=\"height:36px\"/\u003e\u003c/a\u003e\n\n# World's fastest lightweight zero-configuration SPA HTTP server. \n\nSimply serves SPA bundle on HTTP port which makes it play well with Traefik / Cloudflare another reverse proxy.\n\n## Benefits\n\n* Zero-configuration in Docker without managing additional configs\n* 10x times smaller then Nginx, faster startup time, a bit better or same performance\n* Plays well with all popular SPA frameworks and libraries: Vue, React, Angular, Svelte and bundlers: Webpack/Vite\n* Supports Brotly compression on original files, you don't need to archivate files by yourself, it does it for you\n* Written in Go, which makes it fast (no overhead on runtime) and tiny (small binary size)\n* Open-Source commercial friendly MIT license\n* 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.\n* Plays well with CDNs caching (e.g. Clouflare), support for ignoring cache of fixed URLs like service worker\n* Created and maintained by Devforth 💪🏼\n\n## Spa-to-http vs Nginx\n\n| | Spa-to-http | Nginx |\n|---|---|---|\n| Zero-configuration | ✅No config files, SPA serving works out of the box with most optimal settings | ❌Need to create a dedicated config file |\n| Ability to config settings like host, port, compression using Environment variables or CLI | ✅Yes | ❌No, only text config file |\n| Docker image size | ✅13.2 MiB (v1.0.3) | ❌142 MiB (v1.23.1) |\n| Brotli compression out-of-the-box | ✅Yes, just set env BROTLI=true | ❌You need a dedicated module like ngx_brotli |\n\nPerformence 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/)\n\n|  | Spa-to-http | Nginx |\n|---|---|---|\n| Average time from container start to HTTP port availability (100 startups) | ✅1.358 s (11.5% faster) | ❌1.514s |\n| Requests-per-second on 0.5 KiB HTML file at localhost * | ✅80497 (1.6% faster) | ❌79214 |\n| Transfer speed on 0.5 KiB HTML file * | ❌74.16 MiB/sec | ✅75.09 MiB/sec (1.3% faster) |\n| Requests-per-second on 5 KiB JS file at localhost * | ✅66126 (5.2% faster) | ❌62831 |\n| Transfer speed on 5 KiB HTML file * | ✅301.32 MiB/sec (4.5% faster) | ❌288.4 |\n\n## Hello world \u0026 ussage\n\nCreate `Dockerfile` in yoru SPA directory (near `package.json`):\n\n```\nFROM node:20-alpine as builder\nWORKDIR /code/\nADD package-lock.json .\nADD package.json .\nRUN npm ci\nADD . .\nRUN npm run build\n\nFROM devforth/spa-to-http:latest\nCOPY --from=builder /code/dist/ . \n```\n\nTest it locally:\n\n```sh\ndocker build -q . | xargs docker run --rm -p 8080:8080\n```\n\nSo we built our frontend and included it into container based on Spa-to-http. This way gives us great benefits:\n\n* 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)\n* Bundle has only small resulting dist folder, there is no source code and node_modules so countainer is small\n* 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\n\n\n# Example serving SPA with Traefik and Docker-Compose\n\n```\nversion: \"3.3\"\n\nservices:\n\n  traefik:\n    image: \"traefik:v2.7\"\n    command:\n      - \"--providers.docker=true\"\n      - \"--providers.docker.exposedbydefault=false\"\n      - \"--entrypoints.web.address=:80\"\n    ports:\n      - \"80:80\"\n    volumes:\n      - \"/var/run/docker.sock:/var/run/docker.sock:ro\"\n\n  trfk-vue:\n    build: \"spa\" # name of the folder where Dockerfile is located\n    labels:\n      - \"traefik.enable=true\"\n      - \"traefik.http.routers.trfk-vue.rule=Host(`trfk-vue.localhost`)\"\n      - \"traefik.http.services.trfk-vue.loadbalancer.server.port=8080\" # port inside of trfk-vue which should be used\n```      \n\nHow to enable Brotli compression:\n\n```diff \n trfk-vue:\n    build: \"spa\"\n++  command: --brotli\n    labels:\n      - \"traefik.enable=true\"\n      - \"traefik.http.routers.trfk-vue.rule=Host(`trfk-vue.localhost`)\"\n      - \"traefik.http.services.trfk-vue.loadbalancer.server.port=8080\"\n```\nHow to change thresshold of small files which should not be compressed:\n\n```diff \n trfk-vue:\n    build: \"spa\"\n--  command: --brotli\n++  command: --brotli --threshold 500\n    labels:\n      - \"traefik.enable=true\"\n      - \"traefik.http.routers.trfk-vue.rule=Host(`trfk-vue.localhost`)\"\n      - \"traefik.http.services.trfk-vue.loadbalancer.server.port=8080\"\n```\n\nHow to run container on a custom port:\n\n\n```diff \n trfk-vue:\n    build: \"spa\"\n++  command: --brotli --port 8082\n    labels:\n      - \"traefik.enable=true\"\n      - \"traefik.http.routers.trfk-vue.rule=Host(`trfk-vue.localhost`)\"\n--    - \"traefik.http.services.trfk-vue.loadbalancer.server.port=8080\"\n++    - \"traefik.http.services.trfk-vue.loadbalancer.server.port=8082\"\n```\n\nIgnore caching for some specific resources, e.g. prevent Service Worker caching on CDNs like Cloudflare:\n\n\n\n```diff \n trfk-vue:\n    build: \"spa\"\n++  command: --ignore-cache-control-paths \"/sw.js\"\n    labels:\n      - \"traefik.enable=true\"\n      - \"traefik.http.routers.trfk-vue.rule=Host(`trfk-vue.localhost`)\"\n      - \"traefik.http.services.trfk-vue.loadbalancer.server.port=8080\"\n```\n\nThis 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\n\n\n\n## Available Options:\n\n| Environment Variable       | Command                                 | Description                                                                                                                                                                                                                           | Defaults |\n|----------------------------|-----------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|\n| ADDRESS                    | `-a` or `--address`                     | Address to use                                                                                                                                                                                                                        | 0.0.0.0  |\n| PORT                       | `-p` or `--port`                        | Port to listen on                                                                                                                                                                                                                     | 8080     |\n| 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`  |\n| 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`  |\n| THRESHOLD                  | `--threshold \u003cnumber\u003e`                  | Threshold in bytes for gzip and brotli compressions                                                                                                                                                                                   | 1024     |\n| DIRECTORY                  | `-d \u003cstring\u003e` or `--directory \u003cstring\u003e` | Directory to serve                                                                                                                                                                                                                    | `.`      |\n| CACHE_MAX_AGE      | `--cache-max-age \u003cnumber\u003e`      | Set cache time (in seconds) for cache-control max-age header To disable cache set to -1. `.html` files are not being cached                                                                                                           | 604800   |\n| IGNORE_CACHE_CONTROL_PATHS | `--ignore-cache-control-paths \u003cstring\u003e` | Additional paths to set \"Cache-control: no-store\" via comma, example \"/file1.js,/file2.js\"                                                                                                                                            |          |\n| SPA_MODE                   | `--spa` or `--spa \u003cbool\u003e`               | 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`   |\n| CACHE                      | `--cache`                               | When enabled f.Open reads are being cached using Two Queue LRU Cache in bits                                                                                                                                                          | `true`   |\n| CACHE_BUFFER               | `--cache-buffer \u003cnumber\u003e`               | Specifies the maximum size of LRU cache in bytes                                                                                                                                                                                      | `51200`  |\n| LOGGER                     | `--logger`                              | Enable requests logger                                                                                                                                                                                                                | `false`  |\n| LOG_PRETTY                 | `--log-pretty`                          | Print log messages in a pretty format instead of default JSON format                                                                                                                                                                  | `false`  |\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdevforth%2Fspa-to-http","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdevforth%2Fspa-to-http","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdevforth%2Fspa-to-http/lists"}