DEV Community

Cover image for Embed Vite React in Golang binary, with live reload!
Danny Hawkins
Danny Hawkins

Posted on • Edited on

Embed Vite React in Golang binary, with live reload!

I recently followed this excellent article from Pacholo Amit, he documents a method where you can embed the static assets built from Vite using the Echo framework in go, into a single binary.

Whilst his solution was great, it was missing something still for me, I like to be able to make changes in code / css and have it instantly updated in the browser, being able to see the frontend change with live reload is a must have.

I created an example project of my own that starts of using his method but then adds a live reloading method in the first Pull Request https://github.com/danhawkins/go-vite-react-example

The Solution

I solved it in this pr where we run the Vite dev server alongside a proxy mode of the echo server in Golang, meaning that we proxy all requests that are relevant directly to the vite server, but we only do this in dev mode.

In our case dev mode is defined by the environment variable ENV being "dev".

The first step is to create a .env file

ENV=dev
Enter fullscreen mode Exit fullscreen mode

Then we update the frontend.go file likes so

package frontend

import (
    "embed"
    "log"
    "net/url"
    "os"

    _ "github.com/joho/godotenv/autoload"
    "github.com/labstack/echo/v4"
    "github.com/labstack/echo/v4/middleware"
)

var (
    //go:embed dist/*
    dist embed.FS

    //go:embed dist/index.html
    indexHTML embed.FS

    distDirFS     = echo.MustSubFS(dist, "dist")
    distIndexHTML = echo.MustSubFS(indexHTML, "dist")
)

func RegisterHandlers(e *echo.Echo) {
    if os.Getenv("ENV") == "dev" {
        log.Println("Running in dev mode")
        setupDevProxy(e)
        return
    }
    // Use the static assets from the dist directory
    e.FileFS("/", "index.html", distIndexHTML)
    e.StaticFS("/", distDirFS)
    // This is needed to serve the index.html file for all routes that are not /api/*
    // neede for SPA to work when loading a specific url directly
    e.Use(middleware.StaticWithConfig(middleware.StaticConfig{
        Skipper: func(c echo.Context) bool {
            // Skip the proxy if the prefix is /api
            return len(c.Path()) >= 4 && c.Path()[:4] == "/api"
        },
        // Root directory from where the static content is served.
        Root: "/",
        // Enable HTML5 mode by forwarding all not-found requests to root so that
        // SPA (single-page application) can handle the routing.
        HTML5:      true,
        Browse:     false,
        IgnoreBase: true,
        Filesystem: http.FS(distDirFS),
    }))
}

func setupDevProxy(e *echo.Echo) {
    url, err := url.Parse("http://localhost:5173")
    if err != nil {
        log.Fatal(err)
    }
    // Setep a proxy to the vite dev server on localhost:5173
    balancer := middleware.NewRoundRobinBalancer([]*middleware.ProxyTarget{
        {
            URL: url,
        },
    })

    e.Use(middleware.ProxyWithConfig(middleware.ProxyConfig{
        Balancer: balancer,
        Skipper: func(c echo.Context) bool {
            // Skip the proxy if the prefix is /api
            return len(c.Path()) >= 4 && c.Path()[:4] == "/api"
        },
    }))
}

Enter fullscreen mode Exit fullscreen mode

We have a new condition in RegiserHandlers that checks if the env is dev and if so we call setupDevProxy.

All this does is create a balancer group with one member (the vite dev server) and use a skipper function if the path is prefixed with /api. This keeps out api routes isolated.

I found this to work really well so far, and the developer experience is the best I have had so far when working on fullstack web applications.

Top comments (0)