Repository: gohugoio/hugo (language: go)

Issue:
Option to not generate aliases for first page of pagination pages
https://discourse.gohugo.io/t/pagination-seems-to-create-aliases/22422/6?u=razon


Discussion / hints:
We currently have a `paginate` option (meaning: default pagination page size), so we need to consolidate a little. I suggest:

```go
// Loaded from the 'pagination' key in hugo.toml.
type struct PaginationConfig {
  DefaultPageSize int // old paginate
  DisableAliases bool
  Path string // old paginatePath
}
```

And make sure that the old config setup also works.

We also have `paginatePath`.
I just saw something like this in a site config:

```text
[languages.en]
weight = 1
paginatePath = "page-en"

[languages.de]
weight = 2
paginatePath = "page-de"
```

The pagination path is different for each language.  Not sure how that's going to work if we include it in the struct above.
>The pagination path is different for each language. Not sure how that's going to work if we include it in the struct above.

I don't see how that will be a problem, the above will still work. We will want to eventually deprecate `paginatePath` etc., but then the above would look like:

```toml
[languages.de]
weight = 2
[languages.de.pagination]
path = "page-de"
```


Fix patch:
diff --git a/config/allconfig/allconfig.go b/config/allconfig/allconfig.go
index 5ff456d55e5..64288e8878a 100644
--- a/config/allconfig/allconfig.go
+++ b/config/allconfig/allconfig.go
@@ -179,6 +179,9 @@ type Config struct {
 	// Server configuration.
 	Server config.Server `mapstructure:"-"`
 
+	// Pagination configuration.
+	Pagination config.Pagination `mapstructure:"-"`
+
 	// Privacy configuration.
 	Privacy privacy.Config `mapstructure:"-"`
 
@@ -369,6 +372,17 @@ func (c *Config) CompileConfig(logger loggers.Logger) error {
 		return err
 	}
 
+	// Legacy paginate values.
+	if c.Paginate != 0 {
+		hugo.Deprecate("site config key paginate", "Use paginator.defaultPageSize instead.", "v0.128.0")
+		c.Pagination.DefaultPageSize = c.Paginate
+	}
+
+	if c.PaginatePath != "" {
+		hugo.Deprecate("site config key paginatePath", "Use paginator.path instead.", "v0.128.0")
+		c.Pagination.Path = c.PaginatePath
+	}
+
 	c.C = &ConfigCompiled{
 		Timeout:           timeout,
 		BaseURL:           baseURL,
@@ -557,9 +571,11 @@ type RootConfig struct {
 	HasCJKLanguage bool
 
 	// The default number of pages per page when paginating.
+	// Deprecated: Use the Pagination struct.
 	Paginate int
 
 	// The path to use when creating pagination URLs, e.g. "page" in /page/2/.
+	// Deprecated: Use the Pagination struct.
 	PaginatePath string
 
 	// Whether to pluralize default list titles.
diff --git a/config/allconfig/alldecoders.go b/config/allconfig/alldecoders.go
index fc033821eea..46dd59bbc20 100644
--- a/config/allconfig/alldecoders.go
+++ b/config/allconfig/alldecoders.go
@@ -327,6 +327,22 @@ var allDecoderSetups = map[string]decodeWeight{
 			return err
 		},
 	},
+	"pagination": {
+		key: "pagination",
+		decode: func(d decodeWeight, p decodeConfig) error {
+			p.c.Pagination = config.Pagination{
+				DefaultPageSize: 10,
+				Path:            "page",
+			}
+			if p.p.IsSet(d.key) {
+				if err := mapstructure.WeakDecode(p.p.Get(d.key), &p.c.Pagination); err != nil {
+					return err
+				}
+			}
+
+			return nil
+		},
+	},
 	"privacy": {
 		key: "privacy",
 		decode: func(d decodeWeight, p decodeConfig) error {
diff --git a/config/allconfig/configlanguage.go b/config/allconfig/configlanguage.go
index 1d2cb5ce36a..38d2309efa6 100644
--- a/config/allconfig/configlanguage.go
+++ b/config/allconfig/configlanguage.go
@@ -248,12 +248,8 @@ func (c ConfigLanguage) CreateTitle(s string) string {
 	return c.config.C.CreateTitle(s)
 }
 
-func (c ConfigLanguage) Paginate() int {
-	return c.config.Paginate
-}
-
-func (c ConfigLanguage) PaginatePath() string {
-	return c.config.PaginatePath
+func (c ConfigLanguage) Pagination() config.Pagination {
+	return c.config.Pagination
 }
 
 func (c ConfigLanguage) StaticDirs() []string {
diff --git a/config/allconfig/load.go b/config/allconfig/load.go
index b24655ebcfd..edf8295bf9f 100644
--- a/config/allconfig/load.go
+++ b/config/allconfig/load.go
@@ -194,8 +194,8 @@ func (l configLoader) applyDefaultConfig() error {
 		"footnoteAnchorPrefix":                 "",
 		"footnoteReturnLinkContents":           "",
 		"newContentEditor":                     "",
-		"paginate":                             10,
-		"paginatePath":                         "page",
+		"paginate":                             0,  // Moved into the paginator struct in Hugo v0.128.0.
+		"paginatePath":                         "", // Moved into the paginator struct in Hugo v0.128.0.
 		"summaryLength":                        70,
 		"rssLimit":                             -1,
 		"sectionPagesMenu":                     "",
diff --git a/config/commonConfig.go b/config/commonConfig.go
index 1e43b165374..40650c5755e 100644
--- a/config/commonConfig.go
+++ b/config/commonConfig.go
@@ -410,3 +410,15 @@ func DecodeServer(cfg Provider) (Server, error) {
 
 	return *s, nil
 }
+
+// Pagination configures the pagination behavior.
+type Pagination struct {
+	//  Default number of elements per page in pagination.
+	DefaultPageSize int
+
+	// The path element used during pagination.
+	Path string
+
+	// WHether to disable generation of alias for the first pagination page.
+	DisableAliases bool
+}
diff --git a/config/configProvider.go b/config/configProvider.go
index ba10d44dd84..ee6691cf1d8 100644
--- a/config/configProvider.go
+++ b/config/configProvider.go
@@ -52,8 +52,7 @@ type AllProvider interface {
 	DefaultContentLanguageInSubdir() bool
 	IsLangDisabled(string) bool
 	SummaryLength() int
-	Paginate() int
-	PaginatePath() string
+	Pagination() Pagination
 	BuildExpired() bool
 	BuildFuture() bool
 	BuildDrafts() bool
diff --git a/hugolib/site_render.go b/hugolib/site_render.go
index 1cd509fea96..a7ecf89af2b 100644
--- a/hugolib/site_render.go
+++ b/hugolib/site_render.go
@@ -223,7 +223,7 @@ func (s *Site) logMissingLayout(name, layout, kind, outputFormat string) {
 
 // renderPaginator must be run after the owning Page has been rendered.
 func (s *Site) renderPaginator(p *pageState, templ tpl.Template) error {
-	paginatePath := s.conf.PaginatePath
+	paginatePath := s.Conf.Pagination().Path
 
 	d := p.targetPathDescriptor
 	f := p.s.rc.Format
@@ -233,7 +233,7 @@ func (s *Site) renderPaginator(p *pageState, templ tpl.Template) error {
 		panic(fmt.Sprintf("invalid paginator state for %q", p.pathOrTitle()))
 	}
 
-	if f.IsHTML {
+	if f.IsHTML && !s.Conf.Pagination().DisableAliases {
 		// Write alias for page 1
 		d.Addends = fmt.Sprintf("/%s/%d", paginatePath, 1)
 		targetPaths := page.CreateTargetPaths(d)
diff --git a/resources/page/pagination.go b/resources/page/pagination.go
index 4beb96e5094..d24879e6289 100644
--- a/resources/page/pagination.go
+++ b/resources/page/pagination.go
@@ -263,7 +263,7 @@ func splitPageGroups(pageGroups PagesGroup, size int) []paginatedElement {
 
 func ResolvePagerSize(conf config.AllProvider, options ...any) (int, error) {
 	if len(options) == 0 {
-		return conf.Paginate(), nil
+		return conf.Pagination().DefaultPageSize, nil
 	}
 
 	if len(options) > 1 {
@@ -400,7 +400,7 @@ func newPaginationURLFactory(d TargetPathDescriptor) paginationURLFactory {
 		pathDescriptor := d
 		var rel string
 		if pageNumber > 1 {
-			rel = fmt.Sprintf("/%s/%d/", d.PathSpec.Cfg.PaginatePath(), pageNumber)
+			rel = fmt.Sprintf("/%s/%d/", d.PathSpec.Cfg.Pagination().Path, pageNumber)
 			pathDescriptor.Addends = rel
 		}
 


Test patch:
diff --git a/config/allconfig/allconfig_integration_test.go b/config/allconfig/allconfig_integration_test.go
index af4655fe81c..a71a28ed758 100644
--- a/config/allconfig/allconfig_integration_test.go
+++ b/config/allconfig/allconfig_integration_test.go
@@ -102,3 +102,85 @@ suffixes = ["html", "xhtml"]
 	b.Assert(contentTypes.HTML.Suffixes(), qt.DeepEquals, []string{"html", "xhtml"})
 	b.Assert(contentTypes.Markdown.Suffixes(), qt.DeepEquals, []string{"md", "mdown", "markdown"})
 }
+
+func TestPaginationConfigOld(t *testing.T) {
+	files := `
+-- hugo.toml --
+ [languages.en]
+ weight = 1
+ paginatePath = "page-en"
+ 
+ [languages.de]
+ weight = 2
+ paginatePath = "page-de"
+ paginate = 20
+`
+
+	b := hugolib.Test(t, files)
+
+	confEn := b.H.Sites[0].Conf.Pagination()
+	confDe := b.H.Sites[1].Conf.Pagination()
+
+	b.Assert(confEn.Path, qt.Equals, "page-en")
+	b.Assert(confEn.DefaultPageSize, qt.Equals, 10)
+	b.Assert(confDe.Path, qt.Equals, "page-de")
+	b.Assert(confDe.DefaultPageSize, qt.Equals, 20)
+}
+
+func TestPaginationConfigNew(t *testing.T) {
+	files := `
+-- hugo.toml --
+ [languages.en]
+ weight = 1
+ [languages.en.pagination]
+ defaultPageSize = 20
+ [languages.de]
+ weight = 2
+ [languages.de.pagination]
+ path = "page-de"
+
+`
+
+	b := hugolib.Test(t, files)
+
+	confEn := b.H.Sites[0].Conf.Pagination()
+	confDe := b.H.Sites[1].Conf.Pagination()
+
+	b.Assert(confEn.Path, qt.Equals, "page")
+	b.Assert(confEn.DefaultPageSize, qt.Equals, 20)
+	b.Assert(confDe.Path, qt.Equals, "page-de")
+	b.Assert(confDe.DefaultPageSize, qt.Equals, 10)
+}
+
+func TestPaginationConfigDisableAliases(t *testing.T) {
+	files := `
+-- hugo.toml --
+disableKinds = ["taxonomy", "term"]
+[pagination]
+disableAliases = true
+defaultPageSize = 2
+-- layouts/_default/list.html --
+{{ $paginator := .Paginate  site.RegularPages }}
+{{ template "_internal/pagination.html" . }}
+{{ range $paginator.Pages }}
+  {{ .Title }}
+{{ end }}
+-- content/p1.md --
+---
+title: "p1"
+---
+-- content/p2.md --
+---
+title: "p2"
+---
+-- content/p3.md --
+---
+title: "p3"
+---
+`
+
+	b := hugolib.Test(t, files)
+
+	b.AssertFileExists("public/page/1/index.html", false)
+	b.AssertFileContent("public/page/2/index.html", "pagination-default")
+}