Vuego is designed to be safe for concurrent use after initialization.
The *Vue instance is immutable after setup, making it naturally thread-safe:
vue := vuego.NewVue(templateFS).Funcs(vuego.FuncMap{
"formatDate": func(t *time.Time) string {
return t.Format("2006-01-02")
},
})
// Safe to call from multiple goroutines
go vue.Render(w1, "page.html", data1)
go vue.Render(w2, "page.html", data2)
go vue.Render(w3, "other.html", data3)Each call to Render() or RenderFragment() creates its own VueContext with a dedicated stack:
type Vue struct {
templateFS fs.FS // Read-only after initialization
loader *Component // Read-only after initialization
funcMap FuncMap // Read-only after Funcs()
}
type VueContext struct {
stack *Stack // Request-scoped!
BaseDir string
CurrentDir string
FromFilename string
TemplateStack []string
}Each render gets its own isolated stack → No shared mutable state → No race conditions!
func main() {
vue := vuego.NewVue(os.DirFS("templates")).Funcs(vuego.FuncMap{
"formatTime": formatTime,
})
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
data := map[string]any{
"user": getCurrentUser(r),
"timestamp": time.Now(),
}
// Safe - each request gets its own context
vue.Render(w, "index.html", data)
})
http.ListenAndServe(":8080", nil)
}func renderMultiplePages(vue *vuego.Vue, pages []Page) []string {
results := make([]string, len(pages))
var wg sync.WaitGroup
for i, page := range pages {
wg.Add(1)
go func(idx int, p Page) {
defer wg.Done()
var buf bytes.Buffer
data := map[string]any{
"title": p.Title,
"content": p.Content,
}
// Safe - each goroutine gets its own context
vue.Render(&buf, "page.html", data)
results[idx] = buf.String()
}(i, page)
}
wg.Wait()
return results
}✅ Safe for concurrent use:
vue.Render()vue.RenderFragment()- All template functions in
FuncMap - Reading from
templateFS
❌ Not safe after first render:
vue.Funcs()- Call this during initialization only
Best Practice: Set up your Vue instance completely before using it concurrently:
// ✅ Good: Setup then use
vue := vuego.NewVue(fs).Funcs(myFuncs)
// Now safe for concurrent rendering
// ❌ Bad: Modifying during use
vue := vuego.NewVue(fs)
go vue.Render(...) // Started rendering
vue.Funcs(additionalFuncs) // RACE CONDITION!Request-scoped allocation is extremely cheap in Go:
- Each
VueContextis stack-allocated - Garbage collector handles cleanup efficiently
- No mutex contention
- Scales linearly with CPU cores
The test suite includes race detection:
go test ./... -raceAll tests pass with the race detector, ensuring thread safety.