Single-Page Applications (SPA)
Okapi can serve a single-page application (React, Vue, Svelte, Angular, …) alongside your API. Real files (the index document, JS, CSS, images) are served directly, and any unmatched path falls back to the SPA index so the client-side router can take over.
Two entry points are provided:
SPA(prefix, dir)— serve from a directory on disk.SPAFS(prefix, fsys)— serve from anyfs.FS, including anembed.FSfor single-binary deployments.
Register the SPA after your API routes so the API keeps precedence.
Serve from disk
Useful during front-end development:
func main() {
o := okapi.Default()
o.Get("/api/v1/users", listUsers)
// Serves ./web/index.html for "/", "/login", "/users/42", ...
o.SPA("/", "./web")
o.Start()
}
Serve from an embedded filesystem
The recommended approach for production — the whole front-end ships inside the binary:
//go:embed all:web/dist
var dist embed.FS
func main() {
o := okapi.Default()
o.Get("/api/v1/users", listUsers)
o.SPAFS("/", dist, okapi.SPAConfig{
Root: "web/dist", // sub-directory inside the embed.FS
MaxAge: time.Hour, // Cache-Control for assets
})
o.Start()
}
How routing works
For every unmatched GET/HEAD request under the prefix:
- If the path maps to a real file, that file is served.
- Otherwise the SPA index document is returned so the client-side router can handle the route.
Registered API routes always win the match. In addition, the top-level path segment of every registered route is auto-excluded from the fallback, so an unknown path under an API namespace returns 404 instead of silently serving the index. For example, with /api/v1/users registered, /api/v1/missing returns 404 rather than index.html.
Caching
- The index document is always served with
Cache-Control: no-cache, so a new deploy is picked up immediately. - Asset files honour
SPAConfig.MaxAge. WithMaxAge: time.Hour, assets are served withCache-Control: public, max-age=3600. WhenMaxAgeis zero, noCache-Controlheader is added for assets.
Configuration
SPAConfig is optional — the zero value serves index.html and auto-excludes registered API routes.
| Field | Description |
|---|---|
Index | File served for client-side routes. Defaults to index.html. |
Root | Sub-directory inside the fs.FS that holds the built SPA (SPAFS only). |
Exclude | Additional path prefixes that must never fall back to the index. |
DisableAutoExclude | Turn off auto-excluding registered route segments; only Exclude is consulted. |
MaxAge | Cache-Control max-age for asset files. The index is always no-cache. |
Excluding extra paths
o.SPA("/", "./web", okapi.SPAConfig{
Exclude: []string{"/metrics", "/healthz"},
})
A full, runnable example lives in examples/spa.