Search Console multi-tenant en Neges: dominio principal, verificacion y SEO por tenant
Como Neges permite que cada tienda verifique su propiedad en Google Search Console, concentre sus senales SEO en un dominio principal y opere el flujo con Strapi, Storefront SSR y Backoffice.
Cada tienda Neges puede vivir en un subdominio slug.neges.cl y, opcionalmente, en un dominio propio. La implementacion de Search Console multi-tenant resuelve dos problemas al mismo tiempo: cada tenant puede verificar su propiedad de forma self-service y todas las senales SEO quedan concentradas en un unico dominio principal.
Resumen ejecutivo
El principio central es simple: el dominio principal manda. En cada tenant existe un canonicalHost y todo lo que mira Google, compradores o integraciones SEO debe derivar de ese host: canonical, og:url, favicon, JSON-LD, sitemap, robots y redirect 301.
Antes de esta implementacion, canonical, og:url y JSON-LD ya usaban context.canonicalHost con fallback al subdominio, pero sitemap.xml y robots.txt seguian usando el host de la request. Eso podia mezclar senales entre slug.neges.cl y el dominio propio. Ademas, no existia una forma self-service para que el tenant verificara Search Console.
El resultado quedo repartido entre tres repos: neges-store sirve robots, sitemap, el archivo google<token>.html y el 301; neges-strapi guarda el token, expone el contexto y valida permisos; neges-backoffice entrega la UI para pegar el archivo y asignar el permiso search-console.manage.
Que quedo entregado
- Canonical, og:url, sitemap, robots, structured data y 301 derivan del dominio canonico del tenant.
- El host no canonico deja de competir: en fases iniciales conserva rel=canonical y luego redirige con 301 al canonicalHost.
- El store publica el archivo de verificacion /google<token>.html por tenant, resuelto desde el contexto de Strapi.
- El tenant pega el token desde Backoffice en Empresa -> Search Console.
- La escritura del token depende del feature search-console.manage, asignable a roles custom.
- Entre los roles base, solo admin recibe search-console.manage.
- La pagina Empresa del backoffice quedo ligada al selector de empresa activo mediante la ruta /settings/tenant, sin documentId en la URL.
Problema, objetivo y alcance
Una tienda puede responder en https://slug.neges.cl y tambien en https://dominio-propio.cl. Para un comprador eso puede parecer inofensivo; para Google, sin una fuente de verdad clara, son dos origenes que pueden competir por el mismo contenido.
| Punto | Decision |
|---|---|
| Problema principal | Evitar contenido duplicado, senales cruzadas y falta de verificacion de propiedad por tenant. |
| Objetivo SEO | Definir un dominio canonico por tenant y hacer que todo lo SEO-facing lo respete. |
| Objetivo operativo | Permitir verificacion self-service en Google Search Console mediante archivo HTML. |
| Objetivo de seguridad | Controlar la escritura del token mediante un feature RBAC asignable. |
| Fuera de alcance | Bing expuesto en UI, verificacion por meta-tag y verificacion Domain property gestionada desde Neges. |
Conceptos clave
canonicalHost es la fuente de verdad. En Strapi se deriva del dominio primario activo del tenant-domain, ordenado por isPrimary desc. Si no existe un dominio custom activo, cae al subdominio slug.neges.cl.
canonicalHost = dominio primario activo del tenant ?? <slug>.neges.clEl campo primaryDomain todavia puede llegar en el payload por compatibilidad, pero queda deprecado para decidir el canonico. La decision viva es canonicalHost.
| Tipo de propiedad Search Console | Como se verifica | Uso recomendado en Neges |
|---|---|---|
| URL-prefix | Archivo HTML, meta-tag, Google Analytics o Google Tag Manager. | Recomendado para Neges porque cubre un origen exacto como https://dominio.cl y se puede resolver server-side por tenant. |
| Domain property | Registro DNS TXT en el proveedor del dominio. | Opcion avanzada para tenants con acceso al DNS. No requiere backoffice ni archivo HTML, pero queda fuera del producto por ahora. |
Por que archivo HTML y token por tenant
El metodo elegido fue servir /google<token>.html desde Express. Esa ruta queda aislada de Angular, puede eximirse facilmente del redirect 301, no depende del contexto que carga el root de la app por JavaScript y vive en el mismo plano que robots.txt y sitemap.xml.
El token se guarda a nivel tenant porque, tras el 301, solo el dominio principal queda indexado. Una propiedad Search Console por tenant, equivalente al dominio principal, cubre el caso actual. La forma de datos, sin embargo, ya admite una lista {provider, token}[] para soportar multiples cuentas o Bing a futuro.
Arquitectura del flujo
La implementacion cruza store, backend y backoffice. El store responde a Google y compradores; Strapi conserva la fuente de verdad; Backoffice entrega la experiencia self-service y el editor de roles.
Auditoria inicial
| Area revisada | Estado antes | Brecha |
|---|---|---|
| Resolucion de dominios | resolveTenantContext ya distinguia subdomain y custom-domain. | Sin brecha critica. |
| canonical / og:url / JSON-LD | Ya derivaban de context.canonicalHost con fallback al subdominio. | El dominio principal ya mandaba en metadata HTML. |
| sitemap.xml / robots.txt | Eran dinamicos, pero usaban buildRequestOrigin, es decir, el host de la request. | Podian emitir senales cruzadas desde un host no canonico. |
| Redirect host a canonico | No existia. Ambos hosts respondian 200. | La consolidacion dependia solo del canonical tag. |
| Verificacion de propiedad | No existia un mecanismo self-service. | El tenant no podia verificar GSC desde el producto. |
| primaryDomain vs canonicalHost | Ambos estaban mapeados, pero solo canonicalHost decidia. | Duplicacion conceptual: primaryDomain queda deprecado. |
Decisiones de diseno
| Decision | Resultado | Justificacion |
|---|---|---|
| Fuente de verdad del canonico | canonicalHost deriva del dominio primario activo; primaryDomain queda deprecado. | Evita drift y respeta la direccion que el codigo ya usaba. |
| Metodo de verificacion | Archivo HTML por Express para propiedades URL-prefix. | Es SSR-nativo, aislado, eximible del 301 y no depende de JavaScript. |
| Ubicacion del token | Tenant, como lista {provider, token}[]. | Tras el 301 basta una propiedad por tenant y queda espacio para Bing o multiples tokens. |
| Gate de escritura | require-target-tenant-feature(search-console.manage), sin exigir tenant-admin. | El permiso queda realmente asignable a roles custom no-admin. |
| Roles base | Solo admin recibe search-console.manage. | Entre roles base, solo admin conecta Search Console; roles custom pueden recibirlo. |
| Redirect 301 | Fase diferida con NG_CANONICAL_HOST_REDIRECT=false como kill-switch. | Bajo riesgo: canonicalHost solo apunta a dominios status=active. |
| Ajustes de Empresa | Ruta /settings/tenant ligada al selector activo. | Elimina el desfase entre empresa activa y empresa vista. |
Implementacion en neges-store
El commit b3d7742 concentra el trabajo en Express, src/server-app.ts, y en el servicio de sitemap. La implementacion se entiende mejor en tres fases.
| Fase | Archivos | Comportamiento |
|---|---|---|
| Fase 1: sitemap y robots canonicos | src/app/core/seo/sitemap.service.ts y src/server-app.ts. | normalizeHost compara hostnames normalizados; isCanonicalHost decide si el host de la request coincide; buildRobotsTxt quita la linea Sitemap en hosts no canonicos; buildSitemapXml devuelve urlset vacio en hosts no canonicos; resolveCanonicalOrigin usa https://canonicalHost para robots y sitemap. |
| Fase 2: archivo de verificacion | src/server-app.ts, storefront.models.ts y storefront.server-api.ts. | serveSiteVerificationFile matchea GET /google<token>.html y /BingSiteAuth.xml, resuelve el tenant, lee context.siteVerifications y responde 200 text/html cuando el token coincide. |
| Fase 3: redirect 301 | src/server-app.ts. | resolveCanonicalRedirect redirige a https://<canonicalHost><originalUrl> cuando el host difiere, salvo rutas exentas y si el kill-switch esta desactivado. |
- googleTokenMatchesFile acepta el token como google<hash>.html, google<hash> o solo hash.
- readSiteVerifications mapea tenant.searchConsoleVerification de forma defensiva y devuelve null si Strapi aun no expone el campo.
- El host no canonico queda crawleable pero sin Sitemap, para permitir que Google lea rel=canonical.
- Las rutas exentas del 301 incluyen /storefront-api, /__preview, checkout, archivos de verificacion y preview de Studio.
- NG_CANONICAL_HOST_REDIRECT=false desactiva el redirect sin redeploy.
- La seguridad del 301 depende de que canonicalHost solo refleje dominios primarios status=active.
La verificacion reportada para store incluye sitemap.service.spec.ts, server.spec.ts, storefront.server-api.spec.ts, 332 tests verdes y build SSR.
Implementacion en neges-strapi
Strapi es la fuente de verdad del token y del permiso. Los commits ab28d5f y c455fb5 agregan el campo, lo exponen al storefront y crean un endpoint dedicado feature-gated.
| Area | Detalle |
|---|---|
| Content-type tenant | src/api/tenant/content-types/tenant/schema.json agrega searchConsoleVerification como json para guardar [{ provider: "google"|"bing", token }]. |
| Lectura por slug o subdominio | src/policies/resolve-storefront-tenant.ts agrega searchConsoleVerification a fields en findTenantBySlug. |
| Lectura por dominio custom | src/api/tenant-domain/services/tenant-domain.ts agrega searchConsoleVerification en populate.tenant.fields. |
| Payload storefront | src/api/product/services/storefront-product.ts normaliza y devuelve searchConsoleVerification con readStorefrontSearchConsoleVerification. |
| Endpoint de escritura | PUT /api/me/tenants/:documentId/search-console en src/api/tenant/routes/custom-tenant.ts. |
| Policies | global::require-auth y require-target-tenant-feature("search-console.manage"). |
| Controller y service | updateManagedTenantSearchConsole en src/api/tenant/{controllers,services}/tenant.ts. |
- mapTenantSearchConsoleVerification sigue exponiendo el summary para backoffice.
- DEFAULT_FEATURE_DEFINITIONS agrega search-console.manage con nombre Search Console Manage.
- DEFAULT_ROLE_FEATURE_MATRIX entrega el feature solo a admin entre roles base.
- syncDefaultFeatureMatrix corre en bootstrap desde src/index.ts y reconcilia roles del sistema en tenants existentes.
- El editor de roles del backoffice es catalog-driven, por lo que el nuevo feature aparece como asignable sin hardcode adicional.
- MCP-CONTRACT.md documenta el campo y endpoint como aditivos y no consumidos actualmente por neges-mcp.
Implementacion en neges-backoffice
Backoffice entrega la parte visible: un bloque Search Console en ajustes de empresa, guardado contra el endpoint dedicado y protegido por el feature correcto. Los commits involucrados son 2500267, a4d519d, b5f0e3b, b7b0cc8 y 9181f2a.
| Cambio | Detalle |
|---|---|
| Bloque Search Console | En tenant-detail-page, el estado vacio muestra Definir; el estado activo muestra token y acciones Modificar/Quitar. |
| Servicio de escritura | TenantsService.updateSearchConsoleVerification(documentId, token) llama PUT /api/me/tenants/:id/search-console. |
| Copy de publicacion | El host donde se publicara el archivo se muestra solo cuando esta publicado y solo para hosts reales; queda oculto en localhost/dev. |
| Gate por feature | canManageSearchConsole usa tenantStore.hasFeature("search-console.manage") mediante SEARCH_CONSOLE_FEATURE_KEY. |
| Ruta Empresa | /settings/tenants/:documentId pasa a /settings/tenant, leyendo tenantStore.activeTenantDocumentId. |
| Sub-rutas | /settings/tenant/edit, /settings/tenant/create y /settings/tenant/domains. |
| Compatibilidad | Las URLs viejas /settings/tenants/... redirigen a las nuevas. |
| Simplificacion | Se elimina tenants-management-page porque el selector superior ya lista y cambia empresas. |
Flujo: servir el archivo de verificacion
Flujo: canonical y 301
Flujo: sitemap y robots por host canonico
Flujo: permiso RBAC para escribir el token
Setup para el operador del tenant
El operador necesita un rol con Search Console Manage. Entre roles base lo tiene admin; un rol custom puede recibirlo desde el editor de roles.
Agrega una propiedad URL-prefix
En Google Search Console, agrega la URL principal de la tienda. Si tiene dominio propio activo, usa https://tu-dominio.cl. Si solo usa subdominio Neges, usa https://tu-tienda.neges.cl.
Elige Archivo HTML
Google entregara un archivo del tipo google1a2b3c4d5e6f.html.
Pega el archivo en Backoffice
Con la empresa correcta seleccionada arriba, entra a Empresa -> Search Console -> Definir y pega el nombre del archivo.
Espera publicacion
La tienda servira automaticamente el archivo desde su dominio.
Verifica en Search Console
Vuelve a Google Search Console y presiona Verificar.
Envia el sitemap
Cuando la propiedad este verificada, envia https://<tu-dominio-principal>/sitemap.xml.
Modelo RBAC
| Elemento | Valor |
|---|---|
| Feature | search-console.manage, mostrado como Search Console Manage. |
| Roles base | Solo admin lo recibe mediante DEFAULT_ROLE_FEATURE_MATRIX. |
| Roles custom | Se puede activar desde /admin/roles/:id/edit porque el editor consume el catalogo de features. |
| Backend enforcement | PUT /me/tenants/:id/search-console valida require-target-tenant-feature contra el tenant objetivo. |
| Frontend visibility | El bloque se muestra si hasFeature("search-console.manage") para la empresa activa. |
| Bootstrap | Strapi debe desplegarse para que syncDefaultFeatureMatrix sincronice el catalogo y las asignaciones del rol admin. |
Despliegue y operacion
El orden de despliegue recomendado es neges-strapi, luego neges-store y finalmente neges-backoffice. Strapi debe ir primero porque el campo, el endpoint y el feature RBAC deben existir antes de que store y backoffice los usen.
- Desplegar neges-strapi para crear searchConsoleVerification, endpoint dedicado y feature search-console.manage.
- Desplegar neges-store para servir el archivo de verificacion y aplicar canonical, sitemap, robots y 301.
- Desplegar neges-backoffice para exponer el bloque Search Console y el editor de roles con el nuevo feature.
- Usar NG_CANONICAL_HOST_REDIRECT=false en Vercel si hay que desactivar el 301 sin redeploy.
- Recordar que robots.txt, sitemap.xml y archivos de verificacion usan Cache-Control corto de 300 segundos.
- Las paginas publicas usan edge cache con invalidacion por tag tenant:<slug>, segun docs/storefront-performance-plan.md.
Contratos y formas de datos
[
{
"provider": "google",
"token": "google1a2b3c4d5e6f.html"
}
]| Contrato | Forma |
|---|---|
| Strapi tenant | searchConsoleVerification es json y guarda una lista de provider/token. Hoy la UI escribe un token Google; la forma admite provider bing a futuro. |
| Lectura tolerante | Entradas malformadas se descartan tanto en Strapi como en store. |
| Store context | context.siteVerifications expone readonly { provider: "google"|"bing"; token: string }[] | null. |
| Backoffice escritura | PUT /api/me/tenants/:documentId/search-console con body { data: { searchConsoleVerification: <token|null> } }. |
Commits por repo
| Repo | Commit | Contenido |
|---|---|---|
| neges-store | b3d7742 | Fase 1 sitemap/robots canonicos, Fase 2 archivos de verificacion, Fase 3 redirect 301 y kill-switch. |
| neges-strapi | ab28d5f | Campo searchConsoleVerification y exposicion en buildTenantPayload por ambos caminos de resolucion. |
| neges-strapi | c455fb5 | Feature RBAC search-console.manage y endpoint dedicado feature-gated, fuera del update general. |
| neges-backoffice | 2500267 | Bloque Search Console self-service en la pagina de empresa. |
| neges-backoffice | a4d519d | Refinamiento de guards, host de publicacion y copy. |
| neges-backoffice | b5f0e3b | Simplifica copy del host de publicacion y lo oculta en local/dev. |
| neges-backoffice | b7b0cc8 | Gate del bloque por feature search-console.manage y endpoint dedicado. |
| neges-backoffice | 9181f2a | Ajustes de Empresa ligados al selector activo, ruta sin id y eliminacion de lista multi-empresa. |
Limitaciones conocidas y trabajo futuro
- Bing y meta-tag: store soporta provider bing y BingSiteAuth.xml, pero backoffice solo expone un token Google. Es una extension barata a futuro.
- Token por dominio: hoy el token vive en tenant porque basta con el dominio principal tras el 301. Si se quisieran multiples dominios independientes, habria que mover tokens a tenant-domain y exponer una lista por host.
- Domain property gestionada: la verificacion por DNS TXT es manual del tenant. Neges podria mostrar el TXT en backoffice como guia, pero no automatizarlo todavia.
- Fase 3 por dominio: tenant-domain tiene redirectToPrimary, pero el 301 actual usa enfoque global canonicalHost + kill-switch. A futuro se podria honrar redirectToPrimary si se expone en el contexto.
Como retomar la implementacion
| Repo | Archivos clave |
|---|---|
| neges-store | src/server-app.ts; src/app/core/seo/sitemap.service.ts; src/app/core/storefront/storefront.server-api.ts; src/app/core/storefront/storefront.models.ts. |
| neges-strapi | src/api/tenant/content-types/tenant/schema.json; src/policies/resolve-storefront-tenant.ts; src/api/tenant-domain/services/tenant-domain.ts; src/api/product/services/storefront-product.ts; src/api/tenant/{routes,controllers,services}/*tenant*.ts; src/utils/rbac/default-rbac.ts. |
| neges-backoffice | src/app/features/tenants/pages/tenant-detail-page/*; src/app/features/tenants/data-access/tenants.service.ts; src/app/features/tenants/tenants.models.ts; src/app/features/tenants/tenants.routes.ts; src/app/app.routes.ts. |
| Memoria de sesion | search-console-tenant, indexado en MEMORY.md. |
Recursos relacionados
¿Te fue útil este contenido?
Tu feedback nos ayuda a mejorar la documentación de Neges.
¿No encuentras lo que buscas?
Conversa con nuestro equipo de soporte en español. Respondemos por WhatsApp, correo o ticket en menos de 24 horas hábiles.