I´m trying to serve a SPA with Golang and solving the 404 error became a challenge.
The problem is that it seems to be in a loop, when I try to access the app in the browser, it reloads itself indefinitely.
package main
import (
"log"
"net/http"
"os"
"path/filepath"
"time"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/joho/godotenv"
)
var indexBuffer []byte
func main() {
r := chi.NewRouter()
r.Use(middleware.RequestID)
r.Use(middleware.RealIP)
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
fs := http.FileServer(http.Dir(os.Getenv("FRONTEND_PATH")))
r.Handle("/app/static/*", http.StripPrefix("/app/static/", fs))
apiRouter := chi.NewRouter()
apiRouter.Get("/cargas", handlerCargas)
r.Mount("/app/api", apiRouter)
r.NotFound(indexHandler)
log.Fatal(http.ListenAndServe(":8000", r))
}
func init() {
err := godotenv.Load()
if err != nil {
log.Fatal("Erro ao ler .env verifique!")
}
indexBuffer, err = os.ReadFile(filepath.Join(os.Getenv("FRONTEND_PATH"), "index.html"))
if err != nil {
log.Fatal("Erro ao tentar bufferizar a index na init().")
}
}
func indexHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html")
w.Write(indexBuffer)
}
I´m expecting index.html to be served by the indexHandler and the rest of the files be served by the FileServer and errors of page not found be handled by the SPA.
I found a solution here
I adapted my code and used the FileServer as below:
// FileServer para SPA
func FileServerSPA(r chi.Router, public string, static string) {
if strings.ContainsAny(public, "{}*") {
panic("FileServer does not permit URL parameters.")
}
root, _ := filepath.Abs(static)
if _, err := os.Stat(root); os.IsNotExist(err) {
panic("Static Documents Directory Not Found")
}
fs := http.StripPrefix(public, http.FileServer(http.Dir(root)))
if public != "/" && public[len(public)-1] != '/' {
r.Get(public, http.RedirectHandler(public+"/", 301).ServeHTTP)
public += "/"
}
r.Get(public+"*", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
file := strings.Replace(r.RequestURI, public, "/", 1)
if _, err := os.Stat(root + file); os.IsNotExist(err) {
http.ServeFile(w, r, path.Join(root, "index.html"))
return
}
fs.ServeHTTP(w, r)
}))
}
and, in main():
// ...
// after all the endpoints of the api
frontendPath := os.Getenv("FRONTEND_PATH")
FileServerSPA(r, "/app", frontendPath)
log.Fatal(http.ListenAndServe(":8000", r))
Hope it helps