{"record":{"id":"cd89000920bedea4","repo":"glanceapp/glance","slug":"page-slug-s-is-reserved","errorCode":null,"errorMessage":"page slug \"%s\" is reserved","messagePattern":"page slug \"(.+?)\" is reserved","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/glance/glance.go","lineNumber":162,"sourceCode":"\t// Init pages\n\t//\n\n\tapp.slugToPage[\"\"] = &config.Pages[0]\n\n\tproviders := &widgetProviders{\n\t\tassetResolver: app.StaticAssetPath,\n\t}\n\n\tfor p := range config.Pages {\n\t\tpage := &config.Pages[p]\n\t\tpage.PrimaryColumnIndex = -1\n\n\t\tif page.Slug == \"\" {\n\t\t\tpage.Slug = titleToSlug(page.Title)\n\t\t}\n\n\t\tif slices.Contains(reservedPageSlugs, page.Slug) {\n\t\t\treturn nil, fmt.Errorf(\"page slug \\\"%s\\\" is reserved\", page.Slug)\n\t\t}\n\n\t\tapp.slugToPage[page.Slug] = page\n\n\t\tif page.Width == \"default\" {\n\t\t\tpage.Width = \"\"\n\t\t}\n\n\t\tif page.DesktopNavigationWidth == \"\" && page.DesktopNavigationWidth != \"default\" {\n\t\t\tpage.DesktopNavigationWidth = page.Width\n\t\t}\n\n\t\tfor i := range page.HeadWidgets {\n\t\t\twidget := page.HeadWidgets[i]\n\t\t\tapp.widgetByID[widget.GetID()] = widget\n\t\t\twidget.setProviders(providers)\n\t\t}\n","sourceCodeStart":144,"sourceCodeEnd":180,"githubUrl":"https://github.com/glanceapp/glance/blob/91324e8de762702e97b0ac5c8e36271d644d8642/internal/glance/glance.go#L144-L180","documentation":"Startup fails when a page's slug collides with one of glance's reserved slugs (\"login\" or \"logout\"). Slugs derive from the page title via titleToSlug when not explicitly set, so a page literally titled or slugged 'Login' or 'Logout' would shadow the auth endpoints and is rejected.","triggerScenarios":"Declaring `pages: - title: Login` or `- slug: login` (case variants included, since titleToSlug lowercases) while any page exists with that derived or explicit slug. Note the first page also gets registered under \"\" earlier, but the check only guards the reserved list.","commonSituations":"Adding an SSO/status page named 'Login' or 'Logout'; migrating a dashboard whose page titles happen to include reserved words; explicitly setting slug: logout for a session-info page.","solutions":["Rename the page title or set an explicit non-reserved slug (e.g. `sign-in`)","If you want a login-adjacent page, link to the real /login route instead of creating a page","Check all pages' derived slugs: the slug is the lowercased, hyphenated title, so 'LOGOUT' and 'Log Out' both collide"],"exampleFix":"# before\npages:\n  - title: Login\n# after\npages:\n  - title: Sign In\n    slug: sign-in\n","handlingStrategy":"validation","validationCode":"// Pre-check page slugs against the reserved set\nreserved := map[string]bool{\"login\": true, \"logout\": true}\nfor _, p := range cfg.Pages {\n    slug := p.Slug\n    if slug == \"\" { slug = titleToSlug(p.Title) }\n    if reserved[slug] { return fmt.Errorf(\"page %q collides with reserved slug %q\", p.Title, slug) }\n}\n","typeGuard":"func isReservedPageSlug(slug string) bool { return slug == \"login\" || slug == \"logout\" }","tryCatchPattern":"Catch at startup and rename the offending page; the message already includes the colliding slug — no dynamic recovery exists.","preventionTips":["Avoid naming pages 'Login'/'Logout' or slugs that lowercase to them","Prefer explicit unique slugs on all pages","Link to /login and /logout rather than recreating them as pages"],"tags":["pages","config","routing","startup"],"backgroundTag":null,"analyzedSha":"91324e8de762702e97b0ac5c8e36271d644d8642","analyzedAt":"2026-08-15T14:12:54.279Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}