Hey everyone!
I use nspawn instead of docker and prefer one binary deploys. Traefik is used for the reverse proxy without a CDN. This assumes the api
and frontend
directories are next to each other. For example:
~/code.vikunja.io/api/
~/code.vikunja.io/frontend/
In the frontend
, add your domain to window.API_URL
in public/index.html
. Then build it.
yarn run build
The heart of embedding assets lies in pkg/routes/routes.go
in the api
. Add this to the end of the RegisterRoutes()
function. Build and run it. This will pull the assets from the frontend
and serve it up from the same server (same hostname/port).
// Static files
e.Use(middleware.StaticWithConfig(middleware.StaticConfig{
// Root directory from where the static content is served.
// Required.
Root: "../frontend/dist",
// Enable HTML5 mode by forwarding all not-found requests to root so that
// SPA (single-page application) can handle the routing.
// Optional. Default value false.
HTML5: true,
}))
However, the assets live outside the binary. Let’s add them to the binary. Vikunja uses the Echo framework and their Embed Resources Recipe uses go.rice
. Replace the above snippet with this.
// The file server for rice. "../../../frontend/dist" contains the frontend built for production.
staticBox := rice.MustFindBox("../../../frontend/dist")
// Serves static files from the rice box
e.Any("/*", echo.WrapHandler(http.FileServer(staticBox.HTTPBox())))
Add the imports at the top.
import (
...
"github.com/GeertJohan/go.rice"
"net/http"
)
Go ahead and build it but it’s not ready just yet. We need to add the assets to the binary. If you haven’t already, you’ll need to install go.rice.
go get github.com/GeertJohan/go.rice/rice
Now run these commands to append the frontend/dist
files into the binary. This only works if you’re in the same directory as routes.go
.
cd pkg/routes
rice append --exec ../../vikunja
Run the binary and it serve up the assets. But if you refresh the page, you’ll discover a 404 for routes like /login
and /register
. That’s because those are routes, not files and the api
backend doesn’t know how to handle the frontend
routes. Let’s fix that. Add this to after the code above in routes.go
. This will redirect /login
and /register
to the home page which then loads the application.
// Handle specific paths so that a browser refresh doesn't end up with a 404.
e.GET("/login", func(c echo.Context) error {
return c.Redirect(http.StatusSeeOther, "/")
})
e.GET("/register", func(c echo.Context) error {
return c.Redirect(http.StatusSeeOther, "/")
})
The down side to this is the assets are not gzipped. That’s not a big deal for me because Traefik handles the compression. Also, go.rice offers other ways to embed the assets that may be better in certain circumstances.
Enjoy!