{"record":{"id":"8cd03d83e7e26ce1","repo":"gohugoio/hugo","slug":"redirects-must-have-either-from-or-fromre-set","errorCode":null,"errorMessage":"redirects must have either From or FromRe set","messagePattern":"redirects must have either From or FromRe set","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"config/commonConfig.go","lineNumber":262,"sourceCode":"\t\t}\n\t}\n\treturn true\n}\n\nfunc (s *Server) CompileConfig(logger loggers.Logger) error {\n\tif s.compiledHeaders != nil {\n\t\treturn nil\n\t}\n\tfor _, h := range s.Headers {\n\t\tg, err := glob.Compile(h.For)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to compile Headers glob %q: %w\", h.For, err)\n\t\t}\n\t\ts.compiledHeaders = append(s.compiledHeaders, g)\n\t}\n\tfor _, r := range s.Redirects {\n\t\tif r.From == \"\" && r.FromRe == \"\" {\n\t\t\treturn fmt.Errorf(\"redirects must have either From or FromRe set\")\n\t\t}\n\t\trd := redirect{\n\t\t\theaders: make(map[string]glob.Glob),\n\t\t}\n\t\tif r.From != \"\" {\n\t\t\tg, err := glob.Compile(r.From)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to compile Redirect glob %q: %w\", r.From, err)\n\t\t\t}\n\t\t\trd.from = g\n\t\t}\n\t\tif r.FromRe != \"\" {\n\t\t\tre, err := regexp.Compile(r.FromRe)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to compile Redirect regexp %q: %w\", r.FromRe, err)\n\t\t\t}\n\t\t\trd.fromRe = re\n\t\t}","sourceCodeStart":244,"sourceCodeEnd":280,"githubUrl":"https://github.com/gohugoio/hugo/blob/52c9bd7908b4d02d4d0ff8f82a888834d6ee10d2/config/commonConfig.go#L244-L280","documentation":"A hard validation in Server.CompileConfig: every entry in server.Redirects must set at least one of `from` (glob) or `fromRe` (regexp). A redirect with both empty cannot match any path, so Hugo rejects it immediately rather than silently ignoring it.","triggerScenarios":"A `[[server.redirects]]` block that only sets `to` and `status` but omits both `from` and `fromRe`; a redirect entry created as an empty table.","commonSituations":"Building a redirect rule and forgetting the source matcher; refactoring redirects and leaving a stub entry.","solutions":["Add `from = \"/old-path/*\"` (glob) or `fromRe = \"^/old-path/(.*)$\"` (regexp) to the offending redirect.","Remove the redirect entry if it is no longer needed.","Ensure exactly one matcher style is used per redirect (or both, intentionally)."],"exampleFix":"# before\n[[server.redirects]]\nto = \"/new/\"\nstatus = 301\n\n# after\n[[server.redirects]]\nfrom = \"/old/*\"\nto = \"/new/\"\nstatus = 301","handlingStrategy":"validation","validationCode":"python3 - <<'PY'\nimport tomllib\ncfg=tomllib.loads(open('hugo.toml').read())\nfor r in cfg.get('server',{}).get('redirects',[]):\n    assert r.get('from') or r.get('fromRe'), f\"redirect missing from/fromRe: {r}\"\nprint(\"redirects ok\")\nPY","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always set `from` (glob) or `fromRe` (regexp) on every redirect.","Remove placeholder/empty redirect entries."],"tags":["config","server","redirects","validation"],"backgroundTag":null,"analyzedSha":"52c9bd7908b4d02d4d0ff8f82a888834d6ee10d2","analyzedAt":"2026-08-09T21:49:36.660Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}