http.ServeFile is convenient when you’re new to go http applications, prototyping, or have simple needs for your web server. But when your server gets more sophisticated or mature, watch out for legacy calls to http.ServeFile: they can cause seemingly strange bugs as you implement new features.

Let’s say you have a very simple file handler that just calls http.ServeFile:

func FileHandler(w http.ResponseWriter, r *http.Request) {
    http.ServeFile(w, r, basePath + r.URL.Path)

Now as your server gets more developed, instead of handling the request directly with the handlers for each route, you treat each route-specific handler as a callback function, wrapped in some general request-processing code. This is a common early enhancement of Go web applications.

func RequestProcessor(fn http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
         // … pre-processing goes here
         fn(w, r)
         // … post-processing goes here

If fn contains a call to http.ServeFile(w, r, file), like FileHandler does, your post-processing will never occur.

You have a similar issue with http.Error(w, msg, code), though a difference here is that the documentation includes an admonishment not to allow any further writes to w after Error(...) is called. That’s one more reason to create your own custom error response and stop using http.Error – easy enough to do with a template and a simple function that takes in an error message and executes the template.