Use the standard library's http.FileServer wrapped around http.Dir to serve static assets directly from your file system. For production, always prefix the handler with a specific path and consider using a reverse proxy like Nginx for better performance and security.
Here is a minimal example serving files from a ./public directory at the /static URL prefix:
package main
import (
"log"
"net/http"
"path/filepath"
)
func main() {
// Define the directory to serve
fs := http.FileServer(http.Dir("./public"))
// Strip the "/static" prefix so the file system sees the correct path
http.Handle("/static/", http.StripPrefix("/static/", fs))
// Optional: Serve index.html for the root path
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, filepath.Join("./public", "index.html"))
})
log.Println("Server starting on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
If you need to serve files from a custom directory structure or handle specific MIME types, you can wrap the handler logic. However, for most applications, http.StripPrefix is essential to prevent path traversal attacks and ensure the URL path matches the file system path.
For high-traffic production environments, it is often better to serve static files via a dedicated web server like Nginx rather than Go. This offloads I/O from your application and allows you to leverage HTTP/2, gzip compression, and caching headers more efficiently.
Example Nginx configuration to serve the same ./public directory:
server {
listen 80;
server_name example.com;
# Serve static files from /var/www/public
location /static/ {
alias /var/www/public/;
# Enable compression and caching
gzip on;
expires 1y;
add_header Cache-Control "public, immutable";
}
# Proxy API requests to your Go app
location / {
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
In this setup, your Go application only handles dynamic routes, while Nginx handles the heavy lifting for static assets. This separation improves scalability and reduces the memory footprint of your Go process.