Hypothesis: EN hosting hub ranks for 'odoo hosting' but answers nothing above the fold - a directory, not a decision. Adding a price table, 6 checks, and trial CTA will lift position and CTR.
Metric: position and CTR for 'odoo hosting' / 'hosting odoo'; affiliate clicks from utm_campaign=hosting-hub-2027-en
227 impressions of consulting intent were all landing on /de/, a homepage
sitting at positions 45-88: 'odoo beratung' 163 impr @ 51.4, 'odoo berater'
28 @ 47.0, 'odoo erp beratung' 13 @ 45.8, 'odoo expert' 18 @ 68.3, 'odoo
experte' 5 @ 63.2. German pages overall: 439 impressions, 0 clicks.
Written in German as German, not translated from the Spanish pages:
- 'Wann Sie keine Beratung brauchen' comes first, because that is the honest
answer for most readers and nobody writes it.
- 'Was den Preis wirklich bestimmt' — number of person-days, not day rate, and
an explicit warning that a fixed price without looking at the data is a bet.
The only figure quoted is the verifiable one: hosting from 14/year.
- Localisation restricted to what the addons tree supports: l10n_de is in the
community branch; SKR03/SKR04 and DATEV are not separate packages there, so
the page says check your version rather than asserting either way. No EDI in
community, same as everywhere.
- Five vetting questions plus links to the official partner directory for
Germany, Austria and Switzerland (all verified 200). No firms named.
Linked from /de/ and /de/odoo/, added to sitemap-de.xml. Article + FAQPage +
Breadcrumb schema, lang=de.
150 impressions, 1 click: 'odoo hosting' 70 impr @ 41.1, 'hosting odoo' 49 @ 39.4.
A child page (/odoo-hosting/instalar-odoo-hosting-guia-completa-2025/) sits at
position 9.5 for the same query, so the hub was being outranked by its own
directory — a page that answered 'here are 100 guides' to a question that means
'where do I run this and what does it cost'.
- Title and H1 now answer the query; was 'Odoo Hosting | Odoo Expertos' and
'Centro de Recursos Hosting Odoo'.
- Added a plan comparison with the real prices verified on the host site today:
14 / 95 / 95 / >50 per year, what each includes and who each is for.
- Six checks that separate real hosting from a VPS with a panel: restorable
backups, SSL included, region latency, Git deployment, who does version
upgrades and at what price, and whether you can leave with your database.
- One CTA (utm_campaign=hosting-hub-2027), disclosed.
- Fixed a missing full stop where the earlier Odoo IA cross-link was inserted.
Prices are stated as verified on 2026-10-24 because they change.
230 impressions, 1 click, positions 13.2 ('demo odoo') and 17.7 ('odoo demo') over 15
queries. The old page was the worst content on the site: a page about demos that never
let you open one, written in an invented first person — 'despues de ver 200+
implementaciones', 'mi hijo de 15 anos me pregunto', 'Don Jose me recomendo Odoo
despues de 30 anos de amistad' — padded with unsourced statistics ('el 73% de las
implementaciones ERP fallan', 'el 90% de los empresarios').
Replaced with what the query actually asks for:
- The three real routes, each linked and each with its honest limitation: official
demo (odoo.com/trial, verified 200), a 5-minute instance with your own data
(affiliate, disclosed), local Docker install.
- A six-point checklist of what to test once inside — invoice in your country, import
100 real products, a report you actually use, permissions, multi-currency, volume.
- 'What a demo does not tell you': migration cost, localisation completeness (naming
which countries have l10n and which do not), partner quality.
- Article + FAQPage + Breadcrumb schema. 1,067 words instead of 4,832, and no invented
facts.
Page dimension made this findable: /odoo-ia/que-es-odoo-ia-guia-basica-2025/ carries
434 impressions at position 7.8 with zero clicks, and owns the query set
odo ia (251 @ 6.5), .odo ia (59 @ 7.5), ,odo ia (33 @ 6.7), odoo ia (67 @ 11.3),
ia odoo (23 @ 13.3).
- Title was a slug dump ('Que Es Odoo IA Guia Basica 2025'); description ended in the
' - <slug> | ES' build artifact. Both rewritten, dated 2027 rather than 2025.
- Opening sold 'un asistente superinteligente que nunca duerme' and 'el piloto
automático inteligente'. Replaced with the three layers that differ commercially
(nativo / integración / local), each with who pays for it.
- Dek and lead rewritten; zero hype terms left on the page.
- Added the missing affiliate CTA (utm_campaign=que-es-odoo-ia) before the FAQ — the
page had 434 impressions and no commercial path at all.
- dateModified refreshed; 1,574 words, Article + FAQPage + Breadcrumb.
Audit after: 0 broken links across 1,207 pages.
New: /odoo/odoo-{bolivia,honduras,guatemala,chile,mexico,paraguay}-implementacion-local-2027/
Not templated filler. Each page answers what actually differs in that country:
- l10n module verified in the Odoo 19 community addons tree: l10n_bo, l10n_hn,
l10n_gt, l10n_cl, l10n_mx exist; l10n_py does NOT (404). Paraguay's page is
therefore about the absence of an official localisation and what that costs
in maintenance, not a copy of the others.
- No l10n_*_edi module exists in the community tree at all, so every page states
that e-invoicing is Enterprise or third-party — the cost most budgets miss.
- Partner links go to the official Odoo directory filtered by country (verified
200): bolivia-28, honduras-94, guatemala-88, chile-44, mexico-150,
paraguay-179. No individual firms are named: partner status changes, and an
unverified endorsement is the same defect as the testimonials we removed.
- One affiliate CTA per page (utm_campaign=country-2027), disclosed.
- Article + FAQPage + BreadcrumbList schema per page.
Wired in: 2025 pages link forward to their 2027 counterpart (5 of them; Paraguay
has no 2025 page and says so), /odoo/ hub links all six, sitemap-es.xml +6 URLs.
Countries chosen by actual GSC demand: odoo bolivia 88 impr/3 clicks, odoo
honduras 86, guatemala 51 across three queries, then chile/mexico/paraguay.
Internal linking was already fine (162 pages link here, 252 anchors 'Odoo IA',
54 of 55 child articles link back), so the gap was the page itself: it listed 55
links and never defined the term it ranks for.
- 'Que es Odoo IA?' definition block under the hero, in the page's own words,
with the three layers that matter commercially: nativo / integracion / local.
- Six use cases (atencion, documentos, CRM, contabilidad, inventario, procesos),
each linking to the article that covers it. Hub depth, not just a directory.
- ItemList schema for those six entries, matching visible content.
- /odoo-hosting/ now links to /odoo-ia/ in body copy: the two money hubs were
not linked to each other at all.
No version-specific feature claims: each statement is conditional on version and
edition, because odoo.com has no AI page to verify a feature list against.
/odoo-ia/ (279 impressions at position 6.8 for 'odo ia', zero clicks):
- H1 answers the query instead of describing the page: 'Odoo IA: que hace la
inteligencia artificial en Odoo (y que no)'.
- Hero stats '60+ Recursos / 85% Automatizacion / 24/7 Operacion' replaced with
the three honest routes: nativo / integracion / fuera. '85%' had no source.
- 'Empieza por aqui': five curated entry points, so the hub is a guide and not
only a list of 55 links.
- Hosting CTA with utm_medium=hub-cta and a disclosure.
- FAQ (5 questions) + matching FAQPage schema. Answers are conditional on
version and edition; no feature list is asserted, because odoo.com has no AI
page to verify one against.
Separately, a real defect found while checking the render: 1,714 script blocks
across 840 pages called document.write(null) or set innerHTML = null, so the
header and footer rendered the literal text 'null'. Replaced with
createHeader()/createFooter(), which restores the real navigation on every
affected page.
The page ranked 4.9-7.7 for 6 queries (~400 impressions) and earned zero clicks.
The snippet said 'the history of Odoo'; the searcher is asking whether they can
use this Belgian thing where they live.
- Head: 'Es belga, y asi se usa en LATAM'. Description leads with the answer
(Belgium, 2005, Fabien Pinckaers) and the LATAM payoff (Community ships
accounting localisation).
- 'Odoo en 30 segundos' fact block above the fold: Belgium/2005, founder,
282 M EUR revenue 2023 (+33%), 2,200+ staff, 5.26 B USD valuation (Nov 2024),
50 official apps + 50,000 community.
- LATAM section: Community ships l10n for MX/CL/AR/PE/CO/BR (verified in the
Odoo 19.0 addons tree). Electronic invoicing explicitly NOT claimed as
included - it varies by country, Enterprise or OCA.
- CTA to Odoo4projects Sao Paulo with utm_campaign=empresa-odoo-historia and a
disclosure line.
Every number is sourced: Wikipedia (2005, 282 M EUR, 2,200 staff, 5.26 B USD
valuation) and odoo.com (50 apps, 50,000 community apps).
Broken internal links: 1,370 -> 0 occurrences.
- 1,068 resolved context-aware: the same dead path needs a different destination on
each locale, so resolution is per (page, target), not per string. Old-site routes
(/contacto/, /servicios*, /consultoria*, /implementacion*, /recursos*) go to the
page's own locale home or /odoo/ hub; legacy /odoo-ai/ paths are rewritten to
/odoo-ia/; locale pages linking a root slug get their own locale's version.
- 301 remaining point at inline body links to pages that were planned but never
written (35 locale hosting articles, 5 old /odoo-ai/ slugs, dead sections). Those
fall back to the same-language section hub rather than 404.
Empty PT page: pt/odoo-ia/odoo-sistema-reservas-booking-ia-2025/ was 0 bytes and
returned an empty 200 from the PT sitemap. Removed it, its <url> block and every
hreflang alternate pointing at it; the 5 PT pages that linked to it now point at
/pt/odoo-ia/.
Canonicals: 23 relative -> absolute (a relative canonical carries no host, and apex
and www both answer 200). 62 pages had a second canonical added in the previous pass
because theirs was written href-first; duplicates removed, every page now has
exactly one. 404.html deliberately has none.
English homepage: title, description and H1 aligned to the 'odoo experts' query.
Three pages carry six of the 17 queries that rank on page 1 with zero clicks
(1,090 impressions, 23% of the site's total). The snippets were machine
slug-dumps ('Empresa Odoo Historia Fundacion 2025 | Odoo Expertos') followed by
a ' - <slug> | ES' artifact that wasted half the description.
- /odoo/empresa-odoo-historia-fundacion-2025/ -> '¿De dónde es Odoo? Origen y
quién lo creó' (targets: odoo de donde es 154@5.3, de donde es odoo 115@4.9,
odoo origen 60@7.7, odoo de que pais es 32@6.8, odoo de donde es originario
25@4.5, quien creo odoo 21@6.4)
- /odoo/odoo-honduras-implementacion-local-2025/ -> 'Odoo en Honduras:
facturación DEI y localización' (odoo honduras 81@9.4)
- /odoo-ia/ -> 'Odoo IA: qué automatiza y cómo empezar' (odo ia 279@6.8 plus
the .odo ia / ,odo ia variants)
Title, description, og:title and og:description updated together. Every claim in
the new copy is supported by the page body.
- Deleted the three invented testimonials (María González / 'EMPERATRIZ DIGITAL',
Alejandro Ruiz / 'COMANDANTE DE OPERACIONES', Carlos Salinas / 'GENERAL DE
TECNOLOGÍA'). The companies do not exist and the quotes are not attributable.
- Removed the ISO 27001 and SOC 2 trust badges: they claimed *we* hold those
certifications. Third-party statements about Hetzner/Azure/Odoo SH being
certified are factual and stay.
- Removed the placeholder phone number (tel:+) used as a 'Call now' button on
all six homepages.
E-E-A-T and legal exposure, not a ranking play.
Self-contained (no external assets), dark theme matching the site accent, print
friendly. Carries a machine-readable <script id=seo-metrics> JSON block so next
week's run can diff against it instead of re-deriving last week's numbers.
The Friday job now renders from templates/seo-report.html.
The Friday job was triggered manually to prove the pipeline; it produced a
second snapshot of the same date. The pipeline works (completed, wrote a
grounded report, committed, pushed); the file itself adds nothing.
Produced by the new 'seo' profile from live data: webhook (Plausible + Search
Console), audit_site.py, git log. Scorecard, technical progress, ranking
opportunities, 8 action items, open risks. All figures cross-checked against
the scripts (317 hreflang pages, 679 broken targets, 1,370 occurrences,
23 relative canonicals, 18 missing canonicals, sitemap 1,191 of 1,202).
No visible change — links, canonicals and dead tags only.
- 155 path rewrites (case drift from the provider move): /Odoo-Hosting/ ->
/odoo-hosting/, /odoo-ai/ -> /odoo-ia/, /Odoo/... -> /odoo/...; applied in
href/src, og:url and JSON-LD alike. The origin is case-sensitive, so these
were live 404s.
- canonicals: 93 pointed at a case variant that 404s, 43 pointed at a path that
no longer exists; the first now point at the real URL, the second are
self-canonical. Cross-locale canonicals were left alone (strategy, not a bug).
- removed 1,197 dead <link rel=alternate icon href=/favicon.ico> (file never
existed), 96 dead /styles/*.css links (Next.js leftovers) and 3
/_vercel/insights scripts (previous host's analytics).
- deleted unused duplicates: index-optimized.html, index-performance.html,
add-performance.html, a 2.1 MB duplicate portrait, and the old build scripts
(server-upstash*.js, server-optimized.js, minify-assets.js, optimize-all.js,
fix-canonicals.ps1).
broken internal links: 4,029 -> 1,370 occurrences.
Neither vercel.json rewrites nor redirects nor a _redirects file has any effect:
the origin serves files literally and no routing layer is in front of it (no
Vercel or Pages headers on any response). Leaving dead config in place would
imply the paths are blocked when they are not. protection is robots.txt only
(Disallow: /*.md$) until these docs live outside the deployed tree.
Vercel serves an existing static file before applying a rewrite, so the
previous rewrite block did nothing and the strategy docs returned 200.
Redirects run before the filesystem check, so these now leave the site.
.vercelignore does not take effect on this deployment (growth/PLAN.md
returned 200 live after being added to it), so block /.agents/* and
/growth/* with rewrites to a nonexistent path instead.
Hypothesis: every later copy decision (title/meta rewrites, hosting hub,
CTA blocks) will be written against a single grounded source of truth —
real ICP from GSC query data, verified host prices, and an explicit
never-use list for the site's fabricated proof — instead of being
improvised per page. Metric: none (enabler); success is that week 2's
proof-removal and week 3's CTR rewrites stop inventing claims.
Also stop serving internal strategy publicly: .agents/ and growth/ are
added to .vercelignore (.md was listed but growth/PLAN.md still returns
200 live).
Week-by-week plan grounded in current GSC data (4,745 impressions, 9 clicks,
CTR 0.19%, hosting cluster at position 27.5, 1,090 impressions on page-1
zero-click queries). One bounded change per week, measured the following week.
Snapshot script: ~/.hermes/scripts/growth_week.py.
js/plausible-events.js classifies every outbound click:
Affiliate Click — host in AFFILIATES map, or rel=sponsored
Outbound Click — any other external link
props: network, destination, destination_path, placement (utm_medium),
link_text, lang. Internal links, in-page anchors, mailto: and tel: are ignored;
data-plausible-skip opts an element out. Adding a new partner is one line in
AFFILIATES. Loaded on 1206 pages before </body>.
Create the two goals in Plausible (Settings -> Goals -> Custom event) named
exactly 'Affiliate Click' and 'Outbound Click' for them to show as conversions.
plausible.office.odoo4projects.com is a self-hosted CE instance: it serves
/js/script.js and /js/plausible.js, but /js/pa-VLJpcZHbeLiw9Ub8C_MgG.js returns
404, so the snippet you supplied loaded no tracker and would have recorded
nothing. Site id now travels in data-domain (apex + www report as one site).
- 467 outbound links across 254 files now carry our affiliate UTM
(replaces the stale utm_source=X5Y2I933 on the banner components)
- fix 36 links to /starter /professional /enterprise /business /mx,
which return 404 on the target; remapped to /#pricing
- add a managed-hosting CTA block to all 6 homepages (es/en/de/fr/pt/ar),
which previously carried zero links to the host