Fix some default site redirect woes

* The fix for #14357 yesterday sadly had the assumption that default language/version/role always was the first site in the sites matrix. This is common, but not always true.
* Also, that fix forgot to add a redirect the other way when `defaultContentLanguageInSubdir` was disabled, e.g. from `/en/` to `/`.
* This commit also renames config option `DisableDefaultDimensionRedirect` to `DisableDefaultSiteRedirect`. This is stricly a breaking change, but it's only been out for a day, and the old name didn't make much sense.

Fixes #14361
This commit is contained in:
Bjørn Erik Pedersen
2026-01-11 15:30:33 +01:00
parent 66ba63cd5f
commit a775488304
6 changed files with 85 additions and 22 deletions
+1
View File
@@ -0,0 +1 @@
tpl/tplimpl/embedded/templates/**
+4 -3
View File
@@ -607,12 +607,13 @@ type RootConfig struct {
DefaultOutputFormat string
// Disable generation of redirect to the default language when DefaultContentLanguageInSubdir is enabled.
// Note that this currently is an alias for DisableDefaultDimensionRedirect introduced in v0.154.4.
// Note that this currently is an alias for DisableDefaultSiteRedirect introduced in v0.154.5.
// It's not obvious how a more fine grained setup would work.
DisableDefaultLanguageRedirect bool
// Disable generation of redirect to the default dimension when DefaultContentRoleInSubdir or DefaultContentVersionInSubdir or DefaultContentLanguageInSubdir is enabled.
DisableDefaultDimensionRedirect bool
// Disable generation of redirects to the default site when DefaultContentRoleInSubdir or DefaultContentVersionInSubdir or DefaultContentLanguageInSubdir is enabled.
// The default site is the site is the combination of defaultContentLanguage, defaultContentVersion and defaultContentRole.
DisableDefaultSiteRedirect bool
// Disable creation of alias redirect pages.
DisableAliases bool
+8 -2
View File
@@ -138,7 +138,8 @@ func (s *Site) resolveDimensionNames() types.Strings3 {
}
type siteLanguageVersionRole struct {
siteVector sitesmatrix.Vector
siteVector sitesmatrix.Vector
isDefaultLanguage bool
roleInternal roles.RoleInternal
role roles.Role
@@ -186,6 +187,10 @@ func (s siteLanguageVersionRole) Version() versions.Version {
return s.version
}
func (s siteLanguageVersionRole) isDefault() bool {
return s.isDefaultLanguage && s.roleInternal.Default && s.versionInternal.Default
}
func (s *Site) Debug() {
fmt.Println("Debugging site", s.Lang(), "=>")
// fmt.Println(s.pageMap.testDump())
@@ -341,7 +346,8 @@ func NewHugoSites(cfg deps.DepsCfg) (*HugoSites, error) {
frontmatterHandler: frontmatterHandler,
store: hstore.NewScratch(),
siteLanguageVersionRole: &siteLanguageVersionRole{
siteVector: sitesmatrix.Vector{i, 0, 0},
isDefaultLanguage: language.IsDefault(),
siteVector: sitesmatrix.Vector{i, 0, 0},
},
}
+22 -13
View File
@@ -203,8 +203,8 @@ func pageRenderer(
}
}
if p.IsHome() && p.outputFormat().IsHTML && s.siteVector.IsFirst() {
if err = s.renderDefaultDimensionRedirect(p); err != nil {
if p.IsHome() && p.outputFormat().IsHTML && s.isDefault() {
if err = s.renderDefaultSiteRedirect(p); err != nil {
if sendErr(err) {
continue
} else {
@@ -368,18 +368,17 @@ func (s *Site) renderAliases() error {
return w.Walk(context.TODO())
}
// renderDefaultDimensionRedirect creates a redirect to the main dimension's home,
// renderDefaultSiteRedirect creates a redirect to the default site's home,
// depending on if it lives in sub folder (e.g. /en) or not.
func (s *Site) renderDefaultDimensionRedirect(home *pageState) error {
if s.conf.DisableDefaultLanguageRedirect || s.conf.DisableDefaultDimensionRedirect {
// The default site is the site is the combination of defaultContentLanguage,
// defaultContentVersion and defaultContentRole.
func (s *Site) renderDefaultSiteRedirect(home *pageState) error {
if s.conf.DisableDefaultLanguageRedirect || s.conf.DisableDefaultSiteRedirect {
return nil
}
shouldAdd := s.conf.DefaultContentLanguageInSubdir && !s.Conf.IsMultihost()
shouldAdd = shouldAdd || s.conf.DefaultContentVersionInSubdir || s.conf.DefaultContentRoleInSubdir
if !shouldAdd {
return nil
}
addRedirectInRoot := s.conf.DefaultContentLanguageInSubdir && !s.Conf.IsMultihost()
addRedirectInRoot = addRedirectInRoot || s.conf.DefaultContentVersionInSubdir || s.conf.DefaultContentRoleInSubdir
homeLink := home.pageOutput.targetPaths().Link // This doesn't have any baseURL paths in it.
@@ -388,10 +387,20 @@ func (s *Site) renderDefaultDimensionRedirect(home *pageState) error {
var ps []string
if of.Path != "" {
// For OutputFormats with a path, creating more than one alias will easily create path clashes without much value.
ps = []string{paths.AddLeadingAndTrailingSlash(of.Path)}
if addRedirectInRoot {
// For OutputFormats with a path, creating more than one alias will easily create path clashes without much value.
ps = []string{paths.AddLeadingAndTrailingSlash(of.Path)}
}
} else {
ps = []string{"/"}
if addRedirectInRoot {
ps = append(ps, "/")
}
if s.Conf.IsMultilingual() && !s.conf.DefaultContentLanguageInSubdir && !s.Conf.IsMultihost() {
// Create redirect from e.g. /en => /
ps = append(ps, homeLink+s.Lang()+"/")
}
// /guest/v1.0.0/en/
// /,/guest/,/guest/v1.0.0 = /guest/v1.0.0/en/
// /guest/v1.0.0/
@@ -1281,8 +1281,23 @@ defaultContentLanguageInSubDir = true
defaultContentVersionInSubDir = true
defaultContentRoleInSubDir = true
disableDefaultLanguageRedirect = false
disableDefaultDimensionRedirect = false
disableDefaultSiteRedirect = false
defaultContentLanguage = "en"
defaultContentVersion = "v1.0.0"
defaultContentRole = "guest"
[languages]
[languages.en]
weight = 2
[languages.nn]
weight = 1
[versions]
[versions."v1.0.0"]
weight = 2
[versions."v2.0.0"]
weight = 1
[roles]
[roles.guest]
[roles.member]
[outputs]
home = [ 'rss', 'amp', 'html']
-- layouts/home.html --
@@ -1334,7 +1349,7 @@ func TestSitesMatrixRedirectsDisableDefaultLanguageRedirect(t *testing.T) {
}
runTest(strings.ReplaceAll(files, "disableDefaultLanguageRedirect = false", "disableDefaultLanguageRedirect = true"))
runTest(strings.ReplaceAll(files, "disableDefaultDimensionRedirect = false", "disableDefaultDimensionRedirect = true"))
runTest(strings.ReplaceAll(files, "disableDefaultSiteRedirect = false", "disableDefaultSiteRedirect = true"))
}
func TestSitesMatrixRedirectsDefaultContentVersionInBase(t *testing.T) {
@@ -1361,7 +1376,7 @@ defaultContentLanguageInSubDir = true
defaultContentVersionInSubDir = true
defaultContentRoleInSubDir = true
disableDefaultLanguageRedirect = false
disableDefaultDimensionRedirect = false
disableDefaultSiteRedirect = false
defaultContentLanguage = "en"
[languages]
[languages.en]
@@ -1384,3 +1399,34 @@ Home.
b.AssertFileContent("public/en/guest/v1.0.0/index.html", "Home.")
b.AssertFileContent("public/en/guest/v1.0.0/amp/index.html", "Home.")
}
func TestSitesMatrixRedirectsDefaultContentLanguageInBase(t *testing.T) {
t.Parallel()
files := `
-- hugo.yml --
baseURL: https://example.org
disableKinds: [taxonomy]
defaultContentVersionInSubDir: true
defaultContentRoleInSubDir: true
defaultContentLanguage: en
languages:
en:
languageName: English
bn:
languageName: Bengali
fr:
languageName: French
-- layouts/all.html --
All.
`
b := hugolib.Test(t, files)
b.AssertFileContent("public/guest/v1.0.0/en/index.html", "url=https://example.org/guest/v1.0.0/\"", `lang="en"`)
files = strings.ReplaceAll(files, "defaultContentVersionInSubDir: true", "defaultContentVersionInSubDir: false")
b = hugolib.Test(t, files)
b.AssertFileContent("public/guest/en/index.html", "url=https://example.org/guest/\"")
}
+1 -1
View File
@@ -1,4 +1,4 @@
<!doctype html>
<!DOCTYPE html>
<html lang="{{ site.Language.LanguageCode }}">
<head>
<title>{{ .Permalink }}</title>