web frontend for git repositories, written in Go git.pocka.jp/legit.git
3

Configure Feed

Select the types of activity you want to include in your feed.

Compile HTML templates upfront

https://tangled.org/pocka.jp/legit/issues/5

Compiling HTML templates on every request is useless on production
deployment and slows down response time significantly.

This patch makes compile-on-request opt-in and upfront compilation a
default.

On my dev machine (Intel Core Ultra 9 285H), spam_repo_list 1,000
iterations / 30 VUs gets 6ms avg -> sub 1ms avg and spam_repo_summary
1,000 iterations / 30 VUs gets 8.2ms avg -> 2.5ms avg improvements.

Shota FUJI (Jul 14, 2026, 8:27 PM +0900) a4dcd129 d1cb4494

+45 -28
+14
config.yaml
··· 65 65 # Display "Powered by ..." text. 66 66 poweredBy: true 67 67 68 + # Read and compile HTML templates on every request. 69 + # 70 + # By default, legit compiles HTML templates on server startup for performance 71 + # reasons. This is great for most usecases but if you're customizing HTML 72 + # templates, this can be tedious. 73 + # 1) edit HTML template file 74 + # 2) restart legit server 75 + # 3) reload browser page 76 + # You can skip step 2 by setting this option to `true`. 77 + # 78 + # Remember to turning off after customizing HTML templates. HTML templates 79 + # compilation slows your pages response time. 80 + compileTemplatesOnRequest: false 81 + 68 82 server: 69 83 # legit uses this strings for clone URL. 70 84 # `https://<name>/<directory>`
+2 -1
config/config.go
··· 34 34 PoweredBy bool `yaml:"poweredBy"` 35 35 } `yaml:"footer"` 36 36 } `yaml:"ui"` 37 - Server struct { 37 + CompileTemplatesOnRequest bool `yaml:"compileTemplatesOnRequest"` 38 + Server struct { 38 39 Name string `yaml:"name,omitempty"` 39 40 Host string `yaml:"host"` 40 41 Port int `yaml:"port"`
+2
demo/config.yaml
··· 44 44 href: https://github.com/icyphox/legit 45 45 poweredBy: true 46 46 47 + compileTemplatesOnRequest: false 48 + 47 49 server: 48 50 name: example.com 49 51 host: localhost
+5
routes/handler.go
··· 1 1 package routes 2 2 3 3 import ( 4 + "html/template" 4 5 "io/fs" 5 6 "net/http" 6 7 ··· 43 44 staticDir: staticDir, 44 45 templatesDir: templatesDir, 45 46 ugcPolicy: bluemonday.UGCPolicy(), 47 + } 48 + 49 + if !c.CompileTemplatesOnRequest { 50 + d.t = template.Must(template.ParseFS(d.templatesDir, "*")) 46 51 } 47 52 48 53 mux.HandleFunc("GET /", d.Index)
+12 -22
routes/routes.go
··· 32 32 33 33 templatesDir fs.FS 34 34 35 + // t is a compiled templates used by deps.Template(). 36 + // Do not use this; use deps.Template() instead. 37 + t *template.Template 38 + 35 39 // ugcPolicy is a bluemonday policy for user generated content. 36 40 ugcPolicy *bluemonday.Policy 37 41 } ··· 85 89 return summaries[j].LastCommit.Committer.When.Before(summaries[i].LastCommit.Committer.When) 86 90 }) 87 91 88 - t := template.Must(template.ParseFS(d.templatesDir, "*")) 89 - 90 92 data := repoListData{ 91 93 Config: d.c, 92 94 Repositories: summaries, 93 95 } 94 96 95 - if err := t.ExecuteTemplate(w, "repo-list", data); err != nil { 97 + if err := d.Template().ExecuteTemplate(w, "repo-list", data); err != nil { 96 98 log.Println(err) 97 99 return 98 100 } ··· 201 203 } 202 204 } 203 205 204 - t := template.Must(template.ParseFS(d.templatesDir, "*")) 205 - 206 206 if len(commits) >= 3 { 207 207 commits = commits[:3] 208 208 } ··· 221 221 IsGoModule: isGoModule(gr), 222 222 } 223 223 224 - if err := t.ExecuteTemplate(w, "repo-top", data); err != nil { 224 + if err := d.Template().ExecuteTemplate(w, "repo-top", data); err != nil { 225 225 log.Println(err) 226 226 return 227 227 } ··· 273 273 Files: files, 274 274 } 275 275 276 - t := template.Must(template.ParseFS(d.templatesDir, "*")) 277 - 278 - if err := t.ExecuteTemplate(w, "repo-tree-ref", data); err != nil { 276 + if err := d.Template().ExecuteTemplate(w, "repo-tree-ref", data); err != nil { 279 277 log.Println(err) 280 278 return 281 279 } ··· 335 333 relpath = strings.Split(treePath, "/") 336 334 } 337 335 338 - t := template.Must(template.ParseFS(d.templatesDir, "*")) 339 - 340 336 if r.URL.Query().Has("preview") { 341 337 previewType := r.URL.Query().Get("preview") 342 338 ··· 363 359 Content: template.HTML(html), 364 360 } 365 361 366 - if err := t.ExecuteTemplate(w, "repo-blob-ref-html-preview", data); err != nil { 362 + if err := d.Template().ExecuteTemplate(w, "repo-blob-ref-html-preview", data); err != nil { 367 363 log.Println(err) 368 364 return 369 365 } ··· 417 413 } 418 414 } 419 415 420 - if err := t.ExecuteTemplate(w, "repo-blob-ref", data); err != nil { 416 + if err := d.Template().ExecuteTemplate(w, "repo-blob-ref", data); err != nil { 421 417 log.Println(err) 422 418 return 423 419 } ··· 508 504 return 509 505 } 510 506 511 - t := template.Must(template.ParseFS(d.templatesDir, "*")) 512 - 513 507 data := repoLogRefData{ 514 508 Config: d.c, 515 509 Meta: repositoryMeta{ ··· 521 515 Commits: commits, 522 516 } 523 517 524 - if err := t.ExecuteTemplate(w, "repo-log-ref", data); err != nil { 518 + if err := d.Template().ExecuteTemplate(w, "repo-log-ref", data); err != nil { 525 519 log.Println(err) 526 520 return 527 521 } ··· 554 548 return 555 549 } 556 550 557 - t := template.Must(template.ParseFS(d.templatesDir, "*")) 558 - 559 551 data := repoCommitData{ 560 552 Config: d.c, 561 553 Meta: repositoryMeta{ ··· 569 561 Diff: diff, 570 562 } 571 563 572 - if err := t.ExecuteTemplate(w, "repo-commit", data); err != nil { 564 + if err := d.Template().ExecuteTemplate(w, "repo-commit", data); err != nil { 573 565 log.Println(err) 574 566 return 575 567 } ··· 615 607 return 616 608 } 617 609 618 - t := template.Must(template.ParseFS(d.templatesDir, "*")) 619 - 620 610 data := repoRefsData{ 621 611 Config: d.c, 622 612 Meta: repositoryMeta{ ··· 629 619 Branches: branches, 630 620 } 631 621 632 - if err := t.ExecuteTemplate(w, "repo-refs", data); err != nil { 622 + if err := d.Template().ExecuteTemplate(w, "repo-refs", data); err != nil { 633 623 log.Println(err) 634 624 return 635 625 }
+10 -5
routes/template.go
··· 6 6 "net/http" 7 7 ) 8 8 9 - func (d *deps) Write404(w http.ResponseWriter) { 9 + func (d *deps) Template() *template.Template { 10 + if d.t != nil { 11 + return d.t 12 + } 13 + 10 14 t := template.Must(template.ParseFS(d.templatesDir, "*")) 15 + return t 16 + } 11 17 18 + func (d *deps) Write404(w http.ResponseWriter) { 12 19 data := error404Data{ 13 20 Config: d.c, 14 21 } 15 22 16 23 w.WriteHeader(404) 17 - if err := t.ExecuteTemplate(w, "404", data); err != nil { 24 + if err := d.Template().ExecuteTemplate(w, "404", data); err != nil { 18 25 log.Printf("404 template: %s", err) 19 26 } 20 27 } 21 28 22 29 func (d *deps) Write500(w http.ResponseWriter) { 23 - t := template.Must(template.ParseFS(d.templatesDir, "*")) 24 - 25 30 data := error500Data{ 26 31 Config: d.c, 27 32 } 28 33 29 34 w.WriteHeader(500) 30 - if err := t.ExecuteTemplate(w, "500", data); err != nil { 35 + if err := d.Template().ExecuteTemplate(w, "500", data); err != nil { 31 36 log.Printf("500 template: %s", err) 32 37 } 33 38 }