Go-Again is a lightweight live-update solution for Go web applications. It automatically updates your browser content when template files change, making development faster and more efficient. Unlike server hot-reloading tools (like Air) that require restarting the entire Go server, Go-Again only updates the affected content while keeping your server running and preserving client-side state.
Perfect for rapid development of:
- Template modifications
- CSS styling changes
- Content updates
- Layout adjustments
- π₯ Hot reloading of HTML templates, css stylesheets, and static files
- π― Minimal setup required
- π WebSocket-based DOM element replacement
- π Multiple directory watching
- π οΈ Framework agnostic (with growing compatibility list)
- πͺΆ Lightweight with minimal dependencies
- πΎ State Preservation: Maintains client-side state (form inputs, counters, etc.) during updates
To install the module, run the following command:
go get github.com/mpmcintyre/go-again
Then import the package in your main routing component:
import reloader "github.com/mpmcintyre/go-again"
Next, initialize the reloader:
rel, err := reloader.New(
func() { app.LoadHTMLGlob("templates/**/*") },
9000,
reloader.WithLogs(true),
)
if err != nil {
log.Fatal(err)
}
defer rel.Close()
Now that the reloader is set up, you can add directories to watch:
rel.Add("templates/components")
rel.Add("templates/views")
Note: If you are using a tool like air you must ensure that the templates directories are ignored in the .air.toml configuration files.
Then add the LiveReload function to your templates:
app.SetFuncMap(template.FuncMap{
"LiveReload": rel.TemplateFunc()["LiveReload"],
})
Finally, include the LiveReload tag in your base template:
{{ LiveReload }}
You can also tell the HMR script to ignore client-side state values to preserve the view of a value using the data-client-state
tag:
<span data-client-state data-bind="clientCount">0</span>
Checkout the full examles in the example directory
package main
import (
"html/template"
"log"
"net/http"
"github.com/gin-gonic/gin"
reloader "github.com/mpmcintyre/go-again"
)
func main() {
app := gin.Default()
// Initialize reloader
rel, err := reloader.New(
func() { app.LoadHTMLGlob("templates/**/*") },
9000,
reloader.WithLogs(true),
)
if err != nil {
log.Fatal(err)
}
defer rel.Close()
// Watch template directories
rel.Add("templates/components")
rel.Add("templates/views")
rel.Add("static")
// Register LiveReload function
app.SetFuncMap(template.FuncMap{
"LiveReload": rel.TemplateFunc()["LiveReload"],
})
// Load templates
app.LoadHTMLGlob("templates/**/*")
// Routes
count := 0
app.GET("/", func(g *gin.Context) {
count += 1
g.HTML(http.StatusOK, "index.html", gin.H{
"title": "Go Again",
"count": count,
})
})
app.Run(":8080")
}
With your template as:
<!DOCTYPE html>
<html lang="en">
<head>
{{LiveReload}}
<title>{{ .title }}</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta charset="UTF-8" />
<script>
var clientCount = 0;
// Function to update all elements that display the counter
function updateCounterDisplays() {
document
.querySelectorAll('[data-bind="clientCount"]')
.forEach((element) => {
element.textContent = clientCount;
});
}
// Function to increment counter
function incrementCounter() {
console.log("Increment");
clientCount++;
updateCounterDisplays();
}
// Initialize displays when page loads
document.addEventListener("DOMContentLoaded", updateCounterDisplays);
</script>
</head>
<body>
<div>
<div>Server side count: {{.count}}</div>
<div>
Client counter value:
<span data-client-state data-bind="clientCount">0</span>
</div>
<button
8000
onclick="incrementCounter()">Increment Counter</button>
</div>
</body>
</html>
Framework | Compatibility |
---|---|
Gin | β |
Echo | β |
Fiber | β |
Chi | β |
Buffalo | β |
Beego | β |
Gorilla Mux | β |
Iris | β |
Revel | β |
Martini | β |
β - Compatible β - Not tested yet β - Not compatible
Enable or disable logging:
reloader.New(callback, 9000, reloader.WithLogs(true))
Contributions are welcome! Feel free to:
- Fork the repository
- Create a feature branch
- Submit a Pull Request
Please ensure you test your changes and update the framework compatibility table if you've verified compatibility with additional frameworks.
MIT License - see LICENSE file for details.
If you encounter any issues or have questions, please file an issue on GitHub.