Embedding static files
If you’re following along, the first thing to do is create a new ui/efs.go
file:
$ touch ui/efs.go
Then add the following code:
package ui import ( "embed" ) //go:embed "static" var Files embed.FS
The important line here is //go:embed "static"
.
This looks like a comment, but it is actually a special comment directive. When our application is compiled (as part of either go build
or go run
), this comment directive instructs Go to store the files from our ui/static
folder in an embedded filesystem referenced by the global variable Files
.
There are a few important details about this which we need to explain.
The comment directive must be placed immediately above the variable in which you want to store the embedded files.
The directive has the format
go:embed "<path>"
. The path is relative to the.go
file containing the directive, so — in our case —go:embed "static"
embeds the directoryui/static
from our project.You can only use the
go:embed
directive on global variables at package level, not within functions or methods. If you try to use it within a function or method, you’ll get the error"go:embed cannot apply to var inside func"
at compile time.Paths cannot not contain
.
or..
elements, nor may they begin or end with a/
. This essentially restricts you to only embedding files or directories that are within the same directory as the.go
file containing thego:embed
directive.The embedded file system is always rooted in the directory which contains the
go:embed
directive. So, in the example above, ourFiles
variable contains anembed.FS
embedded filesystem and the root of that filesystem is ourui
directory.
Using the embedded static files
Now let’s switch up our application so that it serves our static CSS, JavaScript and image files from the embedded file system — instead of reading them from the disk at runtime.
Open your cmd/web/routes.go
file and update it as follows:
package main import ( "net/http" "snippetbox.alexedwards.net/ui" // New import "github.com/justinas/alice" ) func (app *application) routes() http.Handler { mux := http.NewServeMux() // Use the http.FileServerFS() function to create a HTTP handler which // serves the embedded files in ui.Files. It's important to note that our // static files are contained in the "static" folder of the ui.Files // embedded filesystem. So, for example, our CSS stylesheet is located at // "static/css/main.css". This means that we no longer need to strip the // prefix from the request URL -- any requests that start with /static/ can // just be passed directly to the file server and the corresponding static // file will be served (so long as it exists). mux.Handle("GET /static/", http.FileServerFS(ui.Files)) dynamic := alice.New(app.sessionManager.LoadAndSave, noSurf, app.authenticate) mux.Handle("GET /{$}", dynamic.ThenFunc(app.home)) mux.Handle("GET /snippet/view/{id}", dynamic.ThenFunc(app.snippetView)) mux.Handle("GET /user/signup", dynamic.ThenFunc(app.userSignup)) mux.Handle("POST /user/signup", dynamic.ThenFunc(app.userSignupPost)) mux.Handle("GET /user/login", dynamic.ThenFunc(app.userLogin)) mux.Handle("POST /user/login", dynamic.ThenFunc(app.userLoginPost)) protected := dynamic.Append(app.requireAuthentication) mux.Handle("GET /snippet/create", protected.ThenFunc(app.snippetCreate)) mux.Handle("POST /snippet/create", protected.ThenFunc(app.snippetCreatePost)) mux.Handle("POST /user/logout", protected.ThenFunc(app.userLogoutPost)) standard := alice.New(app.recoverPanic, app.logRequest, commonHeaders) return standard.Then(mux) }
If you save these changes and then restart the application, you should find that everything compiles and runs correctly. When you visit https://localhost:4000
in your browser, the static files should be served from the embedded filesystem and everything should look normal.

If you want, you can also navigate directly to the static files to check that they are still available. For example, visiting https://localhost:4000/static/css/main.css
should display the CSS stylesheet for the webpage from the embedded filesystem.

Additional information
Multiple paths
It’s totally OK to specify multiple paths in one embed directive. For example, we could individually embed the ui/static/css
, ui/static/img
and ui/static/js
directories like so:
//go:embed "static/css" "static/img" "static/js" var Files embed.FS
Embedding specific files
I alluded to this at the start of the chapter, but it’s possible for an embed path to point to a specific file. Embedding isn’t just limited to directories.
For example, lets’s pretend that our ui/static/css
directory contains some additional assets that we don’t want to embed, such as Sass or Less files. In that case, we could embed just the ui/static/css/main.css
file like so:
//go:embed "static/css/main.css" "static/img" "static/js" var Files embed.FS
Wildcard paths
The character *
can be used as a ‘wildcard’ in an embed path. Continuing with the example above, we could rewrite the embed directive so that only .css
files under ui/static/css
are embedded:
//go:embed "static/css/*.css" "static/img" "static/js" var Files embed.FS
Related to that, if you use the wildcard path "*"
without any qualifiers, like this:
//go:embed "*" var Files embed.FS
… then it will embed everything in the current directory, including the .go
file that contains the embed directive itself! Most of the time you don’t want that, so it’s more common to explicitly embed specific subdirectories or files instead.
The all prefix
Finally, if a path is to a directory, then all files in that directory are recursively embedded — except for files with names that begin with .
or _
characters. If you want to include those files too, then you should use the all:
prefix at the start of the path.
//go:embed "all:static" var Files embed.FS