---
name: spa_handler
kind: function
lang: go
domain: infra
version: "1.0.0"
purity: pure
signature: "func SPAHandler(fsys fs.FS, indexFile string) http.Handler"
description: "Retorna un http.Handler que sirve los archivos estaticos de un fs.FS y hace fallback a indexFile cuando el path no existe — patron SPA para React Router y similares."
tags: [http, spa, frontend, embed, fileserver]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: ""
imports: [io/fs, net/http, strings]
params:
  - name: fsys
    desc: "fs.FS (tipico iofs.Sub de embed.FS) que contiene los archivos estaticos de la SPA"
  - name: indexFile
    desc: "ruta dentro de fsys del HTML de fallback (ej. \"index.html\")"
output: "http.Handler que sirve archivos de fsys y hace fallback a indexFile cuando el path no existe — patron SPA para React Router"
tested: true
tests:
  - "sirve archivo estatico existente con status 200"
  - "ruta inexistente hace fallback a index.html con Content-Type text/html"
  - "raiz retorna index.html"
  - "path con .. retorna 400"
test_file_path: "functions/infra/spa_handler_test.go"
file_path: "functions/infra/spa_handler.go"
---

## Ejemplo

```go
//go:embed all:frontend/dist
var staticFiles embed.FS

func main() {
    sub, _ := iofs.Sub(staticFiles, "frontend/dist")
    spa := SPAHandler(sub, "index.html")

    mux := http.NewServeMux()
    mux.Handle("/api/", apiRouter())
    mux.Handle("/", spa)

    http.ListenAndServe(":8080", mux)
}
```

## Notas

Funcion pura — construye el handler a partir de los parametros sin I/O. El handler resultante tiene los siguientes comportamientos:

1. Si el path contiene `..`, responde 400 (defensa contra path traversal).
2. Si el path normalizado corresponde a un archivo real en `fsys` (no directorio), lo sirve con `http.FileServer`.
3. En cualquier otro caso (ruta no existe o es directorio), sirve `indexFile` con `Content-Type: text/html; charset=utf-8` y status 200.
4. Si `indexFile` no puede leerse, responde 500.

Pensado para apps que usan `//go:embed all:frontend/dist` y React Router (o cualquier router del lado del cliente). Centraliza el patron que de otro modo cada app escribiria inline.
