gosingle-page-applicationgo-chi

Serving SPA with Golang and Chi router enters a loop


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.


Solution

  • 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