Troubleshooting¶
The silent failures come first: no error, no log line, no exception — just a result that looks plausible. They are the expensive ones.
Nothing appears where it should¶
| Symptom | Likely cause |
|---|---|
| The [H1] SEO tab does not appear in a content app | The decoration file name does not match the target app's name — not its folder, not its label. Silent: no tab, no log line. Compare with name: inside the app definition. |
| The tab does not appear in page properties, on an instance that is not the H1 platform | The shipped decorations name frontend-md3 in their own path, so on an instance without that light module they never apply — silently. Reproduce them in your own light module; see Developers · Installation. |
| The tab appears but a panel (Metadata or Sitemap) is missing | That feature's decoration was not applied to that particular dialog. The shipped set puts Metadata on Base and FrontPage, and Sitemap on Base, FrontPage and CaseStudyDetail. |
Fields save without complaint, but REST, llms.txt and the FTL all behave as if they were empty |
A composite in the chain is missing itemProvider: currentItemProvider, so the values were written into a child node instead of flat properties on the node. The editor reads them back correctly, which is what makes this one expensive. It must be present at every level. |
| An app or dialog "does not update" after a change | An invalid definition is not replaced — Magnolia keeps the previous one live. Check Tools › Definitions for errors in the file you just edited. |
| The Redirects app is not in the launcher | The admincentral decoration did not apply, or the module did not start. Check Tools › Modules first. |
The sitemap¶
| Symptom | Likely cause |
|---|---|
| A collection's items are missing | Three candidates, all silent: the source workspace has no anonymous read ACL; h1sitemapSourceWorkspace names a workspace that does not exist; or the sitemap's root path does not exist in the source workspace — items are collected under the same root as the sitemap, so /h1/sitemap reads items from /h1 of that workspace. |
| A whole section is missing | Exclude from sitemap is ticked on the section's landing page. It removes the page and its entire subtree, and the traversal does not descend. |
/robots and /llms are listed as normal pages |
Expected: the sitemap only skips the node names sitemap and robots.txt. Tick Exclude from sitemap on the robots and llms pages. |
/sitemap.xml returns 404 |
There is no page named sitemap at that root, or the template is not available in the site (templates.availability.templates). |
/sitemap.xml renders but is empty |
The root has no mgnl:page children the current user can read. On the public instance that means nothing is published, or website has no anonymous read ACL. An empty urlset is a valid answer. |
A <loc> came out as /en.html |
A loc that is only a language prefix is that language's home and must be rendered as a directory URL. The shipped sitemap.ftl handles it by comparing the segment against model.locales; a custom renderer has to do the same. |
x-default points somewhere unexpected |
By design it points at the default language's variant, not at the unprefixed path — except on the home page, where the default language is the bare path. |
| A priority I typed is ignored | The stored value does not match ^(0(\.\d+)?|1(\.0+)?)$, so the service falls back to the computed value. The form validates the same expression, so this usually means the value was written by an import or by hand. |
Metadata¶
| Symptom | Likely cause |
|---|---|
No publisher, and no fallback for ogImage |
h1-sites is absent; or no site's rootWebsite matches; or anonymous cannot read h1-sites. All three degrade silently to the per-node values. |
| A content item's metadata has no globals at all | The content-item channel resolves the site only from marca. Without that query parameter there is no site, so ogSiteName, publisher and the image fallback are all missing. |
404 on a path that exists |
Either the node is not an mgnl:page (asking for a component's path is a 404 by design), or marca prefixed the path into something else. The prefixing is idempotent, so ?marca=M3 with /M3/home stays /M3/home — but ?marca=M3 with /other/home becomes /M3/other/home. |
noindex: false on every response, even where nothing was set |
Expected: noindex and nofollow are primitive booleans and are always serialised. Everything else that is empty is omitted from the JSON rather than returned as null. |
The response has no canonical |
By design. The front end builds it with its own routing; a JCR path is not a URL. The Freemarker include does emit <link rel="canonical">. |
ogImage looks wrong: jcr:2f9c… |
Expected: a damLinkField persists jcr:<uuid> and the headless channel returns the reference raw. Resolve it on the consumer side. |
| Structured data ignores the Schema.org type and the topics | Raw JSON-LD (override) is filled in. It replaces the generated block entirely. |
| The wrong language comes back | The language is resolved from Accept-Language, matched against the current site's configured locales, falling back to the site default. Outside a web context, and when no site resolves, the global i18n configuration is used. |
| An item's title falls back oddly | The chain is h1seoTitle → title → name. Content types usually have name and no title, which is why name is in the chain at all. |
robots.txt and llms.txt¶
| Symptom | Likely cause |
|---|---|
/robots.txt returns 404 |
There is no page named robots at that root. A page that exists with an empty body answers {"content":""}, which is valid and means "allow everything". |
The Sitemap: line appears twice |
It was typed into robots.txt content. Each channel appends it with its own domain; do not store it. |
llms.txt shows a brand's title and description at the root |
Site resolution for llms.txt also matches a site whose rootWebsite starts with the root, so at / every site matches and the longest rootWebsite wins. On a multi-brand instance, build llms.txt per brand (/brand-a/llms). |
A page marked No index is gone from llms.txt but its children are still there |
Expected, and deliberately different from the sitemap: h1seoNoindex drops that page's own entry and keeps descending. |
A section is missing entirely from llms.txt |
Every page in it was excluded, so the section has no entries and is dropped. |
| A dynamic page's children are missing | Expected: a dynamic page emits one entry per collection item and stops descending. |
Redirects¶
| Symptom | Likely cause |
|---|---|
| A rule does not fire | Active is not ticked — the rule is loaded only if the property exists and is true; or a more specific rule wins (exact > prefix > regex, then longest pattern); or you are inside the 60 s TTL on an instance where the observation listener could not be registered. That last one logs a warning once, at load time: "No se pudo registrar el observador". |
A rule filed under /brand-a fires on another brand |
Brand folders are organisational. The virtual URI channel evaluates all active rules with no brand scoping; only the REST channel filters by folder. Encode the brand in sourcePattern if the rule must be brand-specific. |
The target gained a /es I did not write |
On a multilingual site the target always carries a language, chosen as: the target's own → the request's → the site default. That is intentional — a bare path is not canonical and would cost a second redirect. The default language's home stays bare (/). |
A prefix rule redirects the section landing page too |
Expected: prefix matches the pattern itself as well as everything under it. /blog/* catches /blog and /blog/x. |
A regex rule never matches |
The stored pattern is not a valid regex — it is logged and skipped — or it is not anchored, or sourcePattern does not start with ^ and was rejected on save. |
| Re-importing an export duplicated every rule | The name column was dropped or emptied. It is the identity of the row; without it the node name is derived from sourcePattern and a rule created from the app (whose node is called 0, 1, …) is not recognised as the same one. |
| A CSV import put rules in the wrong folder | The marca column wins over the folder open in AdminCentral. That is what makes export → edit → re-import symmetrical; check the column. |
active came back false after an import |
The cell is parsed with Boolean.parseBoolean, so only true (in any casing) is true: 1, yes, sí and x all become false. A blank cell is different — it leaves the stored value untouched on an update, and leaves the property absent on a create, which also means the rule is not applied. |
| The CSV export downloaded everything while a brand folder was selected | The export action needs folderParam: folder to pass the selected path. A folder that does not exist yields an empty file, which is the honest answer rather than a full export in disguise. |
MCP tools¶
| Symptom | Likely cause |
|---|---|
| None of the five tools is listed | h1-mcp-api is not installed. It is an optional dependency: without it there is no McpToolRegistry, nobody reads h1McpTools/, and the rest of the module works unchanged. |
seo_metadata refuses a content type |
Its app has no h1-seo-metadata decoration, so the h1seo* fields are not in its form. The guardrail exists because writing them there would leave properties that neither the editor shows nor the metadata endpoint serves. |
Another content type's tool rejects an h1seo* field |
Correct behaviour: seo_metadata declares those fields as its own, so the content type's tool names it instead of discarding the value silently. |
Creating a sitemap page fails saying one already exists |
Either the exact path exists, or a sibling with a different name already uses the same template. One per brand is enforced. |
create refuses the brand |
marca must be a single segment with no / and no ., and the brand page must already exist in website as an mgnl:page. |
General¶
| Symptom | Likely cause |
|---|---|
| Editing an item renamed its node and broke path references | Magnolia derives the node name from the item's name property. A human-readable or i18n name renames the node on every edit. |
| Everything is fine on author and silently empty in production | The public channels read as the anonymous user, except the redirects, which read in the system context. Check the ACLs in Permissions before looking at the code. |
The CSV import command fails with a ClassNotFoundException after an upgrade |
The 1.0.2 delta did not run. The command stopped being a class of this module; the node has to be replaced, not merely bootstrapped when missing. Check config:/modules/h1-seo/commands/h1-seo/importRedirectsFromCsv — class must be com.habito1.helpers.csv.ui.CsvImportCommand. |