How to Organize Assets and Static Files in Go Web Applications
Use the golang-standards/project-layout convention by placing runtime static files in /web/static, HTML templates in /web/template, compiled front-end bundles in /web/app, and repository assets like logos in /assets to maintain clean separation between Go source code and web resources.
Organizing assets and static files in Go web applications requires a deliberate directory structure that keeps web resources accessible to your HTTP server without cluttering your Go source tree. The golang-standards/project-layout repository defines community-standard conventions for positioning these files outside cmd/, pkg/, and internal/ while supporting both filesystem-based development and embedded production binaries. Following this layout ensures your application can switch between http.FileServer serving and Go 1.16+ embed directives without changing directory paths or application logic.
Standard Directory Structure for Web Assets
The project-layout specification establishes four distinct locations for different asset types, each serving a specific purpose in the development and deployment lifecycle.
/web/static for HTTP Server Assets
Place CSS, JavaScript, images, fonts, and other files that your application serves directly to clients into /web/static. This directory functions as the document root for your HTTP handlers and remains logically isolated from your Go packages, making it easy to cache via CDN or reverse proxy. The repository includes a placeholder at /web/static/.keep to signal this directory should exist in version control even when empty.
/web/template for Server-Side Rendering
Store HTML templates and partials used with text/template or html/template in /web/template. This separation allows you to parse template sets using template.ParseFS or template.ParseGlob without traversing unrelated static assets. The placeholder file /web/template/.keep marks this location for version control.
/web/app for Compiled Front-End Bundles
Single-page applications (SPAs) and other compiled JavaScript artifacts belong in /web/app. This directory holds production builds from React, Vue, or Angular projects that your Go backend serves as static content, distinguishing compiled output from raw source files in /web/static.
/assets for Repository Resources
Use the top-level /assets directory for files that support the repository itself but never serve over HTTP, such as README screenshots, documentation diagrams, and CI badges. As documented in /assets/README.md, these resources exist for presentation and build tooling rather than runtime web serving.
Development and Production Serving Strategies
Go web applications support two primary patterns for delivering assets. The project-layout structure accommodates both without requiring directory reorganization.
Serving from the Filesystem During Development
For local development, serve assets directly from disk to enable rapid iteration without recompiling your binary. Use http.FileServer combined with http.StripPrefix to map URL paths to your web/static directory:
package main
import (
"net/http"
)
func main() {
// Serve everything under /web/static at the URL path /static/
fs := http.FileServer(http.Dir("./web/static"))
http.Handle("/static/", http.StripPrefix("/static/", fs))
http.ListenAndServe(":8080", nil)
}
With this configuration, a file located at web/static/css/style.css becomes accessible at http://localhost:8080/static/css/style.css.
Embedding Assets for Single-Binary Deployment
With Go 1.16+, embed the entire web/static and web/template trees directly into your executable using the //go:embed directive. This approach produces a single distributable file while preserving the logical directory structure:
package main
import (
"embed"
"io/fs"
"net/http"
)
//go:embed web/static/*
//go:embed web/template/*
var embeddedFS embed.FS
func main() {
// Create an http.FileSystem from the embedded static files
sub, _ := fs.Sub(embeddedFS, "web/static")
staticFS := http.FileServer(http.FS(sub))
http.Handle("/static/", http.StripPrefix("/static/", staticFS))
// Load templates from the embedded filesystem
// tmpl, _ := template.ParseFS(embeddedFS, "web/template/*.html")
http.ListenAndServe(":8080", nil)
}
The fs.Sub function isolates the web/static subtree so URL paths remain consistent between development and embedded modes. Templates parse directly from embeddedFS using template.ParseFS without extracting files to disk.
Accessing Repository Assets at Build Time
Assets stored in /assets require standard file system operations since they remain outside the embedded web tree. Access these during build processes or documentation generation:
// Example: reading a logo for inclusion in a generated PDF
data, err := os.ReadFile("assets/logo.png")
if err != nil {
log.Fatal(err)
}
_ = data // use the binary data as needed
Key Implementation Files
The project-layout repository includes specific documentation and placeholders that establish this structure:
/assets/README.md– Explains the distinction between repository assets and runtime web files./web/static/.keep– Preserves the static directory in version control./web/template/.keep– Placeholder for server-side template storage./web/app/.keep– Indicates the location for SPA build artifacts./web/README.md– Provides overview documentation for the web resource hierarchy.
Summary
- Place HTTP-servable static files in
/web/staticand keep server-side templates in/web/templateto maintain separation from Go source code. - Store repository-specific assets like documentation images in
/assets, intentionally excluding them from runtime web serving. - Use
/web/appfor compiled front-end bundles such as React or Vue SPA artifacts. - Serve files directly from disk during development using
http.FileServerandhttp.StripPrefixfor rapid iteration. - Embed assets into your binary using
//go:embeddirectives andembed.FSfor single-executable deployments in production. - Reference the
/web/README.mdand placeholder files in thegolang-standards/project-layoutrepository to ensure your structure aligns with Go community standards.
Frequently Asked Questions
What is the difference between /assets and /web/static?
The /assets directory contains repository-level files like README screenshots and documentation images that support the project presentation but never serve over HTTP. In contrast, /web/static holds runtime assets such as CSS, JavaScript, and images that your Go application serves directly to clients via http.FileServer or embeds into the binary.
How do I serve embedded static files with the correct URL prefix?
Use fs.Sub to extract the specific subdirectory from your embed.FS before passing it to http.FileServer. For example, fs.Sub(embeddedFS, "web/static") isolates the static content, allowing you to strip the /static/ prefix with http.StripPrefix so that web/static/css/style.css maps correctly to /static/css/style.css in the browser.
Can I use this layout with a React or Vue single-page application?
Yes, place your compiled SPA bundle files in /web/app as designated by the project-layout standard. Configure your Go server to serve the index.html and static artifacts from this directory, or move the built assets into /web/static if you prefer serving them alongside other static files using the same http.FileServer handler.
Where should HTML templates be stored in a Go web project?
Store HTML templates and partials in /web/template, a dedicated location separate from static assets. This allows you to parse templates efficiently using template.ParseFS when embedding or template.ParseGlob when serving from disk, maintaining clean organization between your presentation logic and style resources.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →