feat(seo): canonical origin fix, metadata completa y landing pages por lenguaje - #163
Merged
Conversation
playwright-report/ and test-results/ are gitignored, but ESLint does not read .gitignore, so running the e2e suite before lint floods the output with errors from bundled report assets (3030 problems, none of them ours).
Every canonical URL, hreflang alternate and sitemap entry was built from `process.env.NEXT_PUBLIC_APP_URL || "https://weftmap.com"`. The variable is set nowhere, so the fallback shipped: the site told Google that the canonical version of every page lives on a domain that is not the deployment, which suppresses indexing of the site that actually exists. The origin is now a single `SITE_URL` constant. It is deliberately not an environment variable — the origin must not vary per environment (a preview deploy publishing its own canonical is a bug), and the env-var form is what shipped the wrong value silently in the first place. Migrating to a custom domain is a one-line edit that shows up in a diff. On top of that: - metadataBase, so relative asset paths resolve absolutely. - buildMetadata() composes title, description, canonical, hreflang, OpenGraph and Twitter card from one computation, so og:url and rel=canonical cannot drift apart. Titles and descriptions come from the i18n dictionaries in all six locales instead of one hardcoded English string. - The OG image is declared explicitly rather than via Next's file convention: setting `openGraph` in a child segment replaces the parent's whole openGraph object, so a file-convention image reaches only the homepage and silently disappears everywhere else. The route lives under [lang]/ because at the app root it would be extension-less and the proxy would 307 it, which several social crawlers do not follow. - JSON-LD (SoftwareApplication, Organization, FAQPage, TechArticle, BreadcrumbList) carrying the per-request CSP nonce. FAQ markup is generated from the same dictionary array the visible FAQ renders, so it cannot describe absent content. graphs/[id] gets none: it renders user-supplied titles. - robots.txt disallows /api and the auth-gated /graphs routes, which also emit noindex. The sitemap drops them, drops the redirecting /docs, carries per-entry hreflang, and stamps lastModified at build time. - Java was missing from the supported-languages copy and the hero chips in all six locales, and the subtitle still claimed six languages. Titles and descriptions are length-checked by test: translations run 15-20% longer than their English source, and 200+ strings cannot be eyeballed.
"call graph generator" is a crowded, low-intent query dominated by IDE vendors and academic tooling. "Python call graph generator" is narrower, has clearer intent, and Weftmap can plausibly be the best answer for it — which is the only kind of query worth targeting from a standing start with no domain authority. Adds /[lang]/call-graph/[language] for the seven supported analyzers, across all six locales: 42 prerendered pages, each with its own H1, title, description and prose, a real analyzable snippet, a CTA into /[lang]/app and links into the docs. The homepage language table now links to them, since it is the strongest internal link source the site has. SQL is the deliberate exception: its analyzer emits an entity-relationship diagram, not a call graph, so its page targets the schema query instead. Calling it a call graph would rank for the wrong intent and bounce. Two tests keep this honest. LANDING_LANGUAGES is checked against the analysis registry, so an eighth language cannot ship without a landing page or an explicit exclusion — the repo's whole pitch is that adding a language takes a few lines, and a hand-maintained parallel list would silently fall behind. And every snippet is run through its real analyzer, asserting at least two nodes and one edge: the tripwire against the thin programmatic content Google's scaled-content policy targets. The breadcrumb has two levels rather than three. A "Call graph" middle position would need an item URL, and /[lang]/call-graph does not exist; pointing a breadcrumb at a 404 is worse than a shorter trail. A real hub page there would restore it and is worth considering separately.
Contributor
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (30)
📝 WalkthroughWalkthroughThe PR adds localized SEO metadata, JSON-LD structured data, language-specific call-graph landing pages, Open Graph and manifest routes, crawler controls, sitemap generation, and automated validation across unit, integration, and end-to-end tests. ChangesSEO and landing pages
Estimated code review effort: 4 (Complex) | ~60 minutes Possibly related issues
Possibly related PRs
Suggested reviewers: ✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Qué hace
Arregla el bug que impedía la indexación y monta el SEO completo del sitio.
El bug. Cada canonical, hreflang y entrada del sitemap se construía con
process.env.NEXT_PUBLIC_APP_URL || "https://weftmap.com". La variable no estádefinida en ningún lado, así que ganaba el fallback: el sitio le decía a Google
que la versión canónica de cada página vive en un dominio que no es el deploy.
Eso suprime la indexación del sitio que sí existe. Verificar la propiedad en
Search Console no lo arregla.
Ahora el origen es una constante
SITE_URLúnica. Deliberadamente no es una envvar: el origen no debe variar por entorno (un preview publicando su propio
canonical es un bug), y la forma env var es justamente la que dejó pasar el valor
equivocado en silencio. Migrar a dominio propio es una línea que aparece en el diff.
Sobre eso:
metadataBase, títulos y descripciones localizados en los 6 idiomas desde losdiccionarios, en lugar de un único string en inglés hardcodeado.
buildMetadata()compone title, description, canonical, hreflang, OpenGraph yTwitter card en un solo cálculo, así
og:urlyrel=canonicalno pueden divergir.next/og, másapple-iconymanifest.webmanifest.con el nonce del CSP por request. El FAQ se genera del mismo array que renderiza
la sección visible, así que no puede describir contenido ausente.
robots.txtexcluye/apiy las rutas/graphscon auth, que además emitennoindex. El sitemap las descarta, descarta/docs(que es un redirect),lleva hreflang por entrada y estampa
lastModifieden build./[lang]/call-graph/[language], 7 lenguajes x 6 locales.y el subtítulo seguía diciendo seis lenguajes.
Tipo
Checklist
pnpm lintpasapnpm typecheckpasapnpm testpasa (612 tests, 16 archivos)pnpm buildpasa (122 páginas prerenderizadas)Verificación adicional
arendir="rtl"Tres cosas que solo aparecieron al implementar
og:imagedesaparecía de todas las rutas menos la home. DefiniropenGraphen un segmento hijo reemplaza el objeto entero del padre en Next, así que la
imagen del file convention solo llegaba a la home. Ahora se declara explícita en
buildMetadata. Se habría descubierto en producción con las previews rotas.El
<script>de JSON-LD rompía la hidratación. El navegador vacía elnoncedel DOM a propósito (anti-exfiltración) y React lo lee como mismatch. Resuelto con
suppressHydrationWarning, el mismo patrón que ya usa el layout en su script detema. Por eso el test del nonce mira el HTML del servidor y no el DOM.
opengraph-image.tsxva bajo[lang]/, no en la raíz. En la raíz la rutasería
/opengraph-image, sin extensión, y el matcher deproxy.tsla redirigiríacon 307 — que varios crawlers sociales no siguen. Se vería bien en el navegador y
rota en cada tarjeta social.
Desviaciones conscientes
/[lang]/call-graph, que no existe. Apuntar un breadcrumb a un 404 es peor queacortarlo. Una página hub ahí lo restauraría y vale como trabajo aparte.
Staticen la tabla de rutas. El layout leeheaders()para el nonce del CSP, lo que marca todo el árbol como dinámico, incluidas las
rutas que ya existían. Hacerlas estáticas significaría quitar el nonce por request:
cambiar un control de seguridad real por un detalle de caché. Se prerenderizan
igual y el HTML llega completo al crawler.
/docsfuera del sitemap — es unredirect()a/docs/introduction.pnpm dev(config existente), no contra build de producción.El nonce y la ausencia de
unsafe-inlineson idénticos en ambos; solo difiereunsafe-eval.Después del merge
Queda trabajo que necesita el deploy: verificar
/robots.txty/sitemap.xmlenvivo, enviar el sitemap en Search Console anotando el baseline de impresiones,
Rich Results Test, debugger de tarjeta social y Lighthouse.
85c3a2bpor sí solo es lo que desbloquea la indexación y es desplegable aparte.Summary by CodeRabbit
New Features
Bug Fixes
Tests