You are reading the docs for rws 18.33 The latest release is 18.34. Read this page for the latest release.

Cookbook: single-page app + API

A React, Vue or Svelte build served with client-side routing, and an API proxied under /api on the same host.

The front end is a directory of built files; deep links like /orders/42 must get index.html (the client-side router takes over), while /api/* goes to the backend:

# run rws in the build directory (dist/, build/)
port = 443
http_redirect_port = 80
spa_fallback = "index.html"                 # unknown paths get index.html…
spa_fallback_exclude_prefixes = ["/api"]    # …but /api keeps its real 404s

[acme]
domains = ["app.example.com"]
email   = "ops@example.com"

[[upstream]]
name = "api"
backends = ["127.0.0.1:8080"]
[upstream.health_check]
path = "/healthz"

[[route]]
name = "api"
[route.match]
path = "/api/*"
[route.action]
type = "proxy"
upstream = "api"
[route.middleware.rate_limit]
max_requests = 120
window_secs  = 60

Requests that match no route — the app's files and its client-side routes — fall through to the static server. For an app under a path of a bigger site (/cloud/ beside ordinary pages), use spa_fallback_routes = ["/cloud=cloud/index.html"] instead, so the site's other misses keep their 404s (Several apps on one server). Same origin, so no CORS settings are needed. When the API lives on another host (api.example.com), allow the app's origin there instead:

port = 443

[cors]
allow_origins     = "https://app.example.com"
allow_methods     = "GET,POST,PUT,PATCH,DELETE,OPTIONS"
allow_headers     = "content-type,authorization"
allow_credentials = true

[[upstream]]
name = "api"
backends = ["127.0.0.1:8080"]

[[route]]
[route.match]
host = "api.example.com"
path = "/*"
[route.action]
type = "proxy"
upstream = "api"

Edit this page on GitHub