Repository: gohugoio/hugo (2026-04-08T10:12:39Z)

Issue:
Layout /tags/list.html panics on edit
See https://discourse.gohugo.io/t/hugo-panic-template-tags-list-html-not-parsed/56971

The following test panics:

```go
// Issue 14740.
func TestEditTagsList(t *testing.T) {
	files := `
-- hugo.toml --
baseURL = "https://example.com"
-- layouts/tags/list.html --
Foo.
`

	b := hugolib.TestRunning(t, files)
	b.AssertFileContent("public/tags/index.html", "Foo.")
	b.EditFileReplaceAll("layouts/tags/list.html", "foo", "Bar").Build()
	b.AssertFileContent("public/tags/index.html", "Bar.")
}
```

OK, so the issue is editing the "legacy mapped" layout names that's also valid in the new setup ...


Discussion:

Patch:
diff --git a/tpl/tplimpl/templates.go b/tpl/tplimpl/templates.go
index 043d4a491e2..be6487b28e6 100644
--- a/tpl/tplimpl/templates.go
+++ b/tpl/tplimpl/templates.go
@@ -45,8 +45,8 @@ var embeddedTemplatesAliases = map[string][]string{
 	"_shortcodes/twitter.html": {"_shortcodes/tweet.html"},
 }
 
-func (s *TemplateStore) parseTemplate(ti *TemplInfo, replace bool) error {
-	err := s.tns.doParseTemplate(ti, replace)
+func (s *TemplateStore) parseTemplate(ti *TemplInfo) error {
+	err := s.tns.doParseTemplate(ti)
 	if err != nil {
 		return s.addFileContext(ti, "parse of template failed", err)
 	}
@@ -69,7 +69,7 @@ func (t *templateNamespace) newBlankTemplate(ti *TemplInfo) tpl.Template {
 	return tt
 }
 
-func (t *templateNamespace) doParseTemplate(ti *TemplInfo, replace bool) error {
+func (t *templateNamespace) doParseTemplate(ti *TemplInfo) error {
 	if !ti.noBaseOf || ti.category == CategoryBaseof {
 		// Delay parsing until we have the base template.
 		return nil
@@ -84,7 +84,7 @@ func (t *templateNamespace) doParseTemplate(ti *TemplInfo, replace bool) error {
 
 	if ti.D.IsPlainText {
 		prototype := t.parseText
-		if !replace && prototype.Lookup(name) != nil {
+		if prototype.Lookup(name) != nil {
 			name += "-" + strconv.FormatUint(t.nameCounter.Add(1), 10)
 		}
 		templ, err = prototype.New(name).Parse(ti.content)
@@ -93,7 +93,7 @@ func (t *templateNamespace) doParseTemplate(ti *TemplInfo, replace bool) error {
 		}
 	} else {
 		prototype := t.parseHTML
-		if !replace && prototype.Lookup(name) != nil {
+		if prototype.Lookup(name) != nil {
 			name += "-" + strconv.FormatUint(t.nameCounter.Add(1), 10)
 		}
 		templ, err = prototype.New(name).Parse(ti.content)


Test patch:
diff --git a/hugolib/rebuild_test.go b/hugolib/rebuild_test.go
index 537c00cef78..b3f36d44246 100644
--- a/hugolib/rebuild_test.go
+++ b/hugolib/rebuild_test.go
@@ -2126,3 +2126,18 @@ Content before shortcode.
 	b.EditFileReplaceAll("content/p1.md", "Content before shortcode.", "This is {{< year >}} wow.").Build()
 	b.AssertFileContent("public/p1/index.html", "Single: P1|", fmt.Sprintf("This is %s wow.", currentYear))
 }
+
+// Issue 14740.
+func TestRebuildEditTagsListLayout(t *testing.T) {
+	files := `
+-- hugo.toml --
+baseURL = "https://example.com"
+-- layouts/tags/list.html --
+Foo.
+`
+
+	b := TestRunning(t, files)
+	b.AssertFileContent("public/tags/index.html", "Foo.")
+	b.EditFileReplaceAll("layouts/tags/list.html", "Foo", "Bar").Build()
+	b.AssertFileContent("public/tags/index.html", "Bar.")
+}
diff --git a/tpl/tplimpl/templatestore.go b/tpl/tplimpl/templatestore.go
index 57a0c4f6990..0c324fd843e 100644
--- a/tpl/tplimpl/templatestore.go
+++ b/tpl/tplimpl/templatestore.go
@@ -149,7 +149,7 @@ func NewStore(opts StoreOptions, siteOpts SiteOptions) (*TemplateStore, error) {
 	if err := s.insertEmbedded(); err != nil {
 		return nil, err
 	}
-	if err := s.parseTemplates(false); err != nil {
+	if err := s.parseTemplates(); err != nil {
 		return nil, err
 	}
 	if err := s.extractInlinePartials(false); err != nil {
@@ -740,7 +740,7 @@ func (s *TemplateStore) RefreshFiles(include func(fi hugofs.FileMetaInfo) bool)
 	if err := s.createTemplatesSnapshot(); err != nil {
 		return err
 	}
-	if err := s.parseTemplates(true); err != nil {
+	if err := s.parseTemplates(); err != nil {
 		return err
 	}
 	if err := s.extractInlinePartials(true); err != nil {
@@ -1627,7 +1627,7 @@ func (s *TemplateStore) addTransformedTemplateSetTree(this *TemplInfo, root *par
 	return tree, nil
 }
 
-func (s *TemplateStore) parseTemplates(replace bool) error {
+func (s *TemplateStore) parseTemplates() error {
 	if err := func() error {
 		// Read and parse all templates.
 		for _, v := range s.treeMain.All() {
@@ -1635,7 +1635,7 @@ func (s *TemplateStore) parseTemplates(replace bool) error {
 				if vv.state == processingStateTransformed {
 					continue
 				}
-				if err := s.parseTemplate(vv, replace); err != nil {
+				if err := s.parseTemplate(vv); err != nil {
 					return err
 				}
 			}
@@ -1655,7 +1655,7 @@ func (s *TemplateStore) parseTemplates(replace bool) error {
 						// The regular expression used to detect if a template needs a base template has some
 						// rare false positives. Assume we don't need one.
 						vv.noBaseOf = true
-						if err := s.parseTemplate(vv, replace); err != nil {
+						if err := s.parseTemplate(vv); err != nil {
 							return err
 						}
 						continue
@@ -1684,7 +1684,7 @@ func (s *TemplateStore) parseTemplates(replace bool) error {
 				if vvv.state == processingStateTransformed {
 					continue
 				}
-				if err := s.parseTemplate(vvv, replace); err != nil {
+				if err := s.parseTemplate(vvv); err != nil {
 					return err
 				}
 			}