Blog Arquitectura Neges

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.

24 min de lectura

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.

PuntoDecision
Problema principalEvitar contenido duplicado, senales cruzadas y falta de verificacion de propiedad por tenant.
Objetivo SEODefinir un dominio canonico por tenant y hacer que todo lo SEO-facing lo respete.
Objetivo operativoPermitir verificacion self-service en Google Search Console mediante archivo HTML.
Objetivo de seguridadControlar la escritura del token mediante un feature RBAC asignable.
Fuera de alcanceBing 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.

canonical-host.txttext
canonicalHost = dominio primario activo del tenant ?? <slug>.neges.cl

El 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 ConsoleComo se verificaUso recomendado en Neges
URL-prefixArchivo 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 propertyRegistro 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.

search-console-arquitectura.mmdmermaid
El diagrama se renderiza al cargar la pagina.

Auditoria inicial

Area revisadaEstado antesBrecha
Resolucion de dominiosresolveTenantContext ya distinguia subdomain y custom-domain.Sin brecha critica.
canonical / og:url / JSON-LDYa derivaban de context.canonicalHost con fallback al subdominio.El dominio principal ya mandaba en metadata HTML.
sitemap.xml / robots.txtEran dinamicos, pero usaban buildRequestOrigin, es decir, el host de la request.Podian emitir senales cruzadas desde un host no canonico.
Redirect host a canonicoNo existia. Ambos hosts respondian 200.La consolidacion dependia solo del canonical tag.
Verificacion de propiedadNo existia un mecanismo self-service.El tenant no podia verificar GSC desde el producto.
primaryDomain vs canonicalHostAmbos estaban mapeados, pero solo canonicalHost decidia.Duplicacion conceptual: primaryDomain queda deprecado.

Decisiones de diseno

DecisionResultadoJustificacion
Fuente de verdad del canonicocanonicalHost deriva del dominio primario activo; primaryDomain queda deprecado.Evita drift y respeta la direccion que el codigo ya usaba.
Metodo de verificacionArchivo HTML por Express para propiedades URL-prefix.Es SSR-nativo, aislado, eximible del 301 y no depende de JavaScript.
Ubicacion del tokenTenant, como lista {provider, token}[].Tras el 301 basta una propiedad por tenant y queda espacio para Bing o multiples tokens.
Gate de escriturarequire-target-tenant-feature(search-console.manage), sin exigir tenant-admin.El permiso queda realmente asignable a roles custom no-admin.
Roles baseSolo admin recibe search-console.manage.Entre roles base, solo admin conecta Search Console; roles custom pueden recibirlo.
Redirect 301Fase diferida con NG_CANONICAL_HOST_REDIRECT=false como kill-switch.Bajo riesgo: canonicalHost solo apunta a dominios status=active.
Ajustes de EmpresaRuta /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.

FaseArchivosComportamiento
Fase 1: sitemap y robots canonicossrc/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 verificacionsrc/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 301src/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.

AreaDetalle
Content-type tenantsrc/api/tenant/content-types/tenant/schema.json agrega searchConsoleVerification como json para guardar [{ provider: "google"|"bing", token }].
Lectura por slug o subdominiosrc/policies/resolve-storefront-tenant.ts agrega searchConsoleVerification a fields en findTenantBySlug.
Lectura por dominio customsrc/api/tenant-domain/services/tenant-domain.ts agrega searchConsoleVerification en populate.tenant.fields.
Payload storefrontsrc/api/product/services/storefront-product.ts normaliza y devuelve searchConsoleVerification con readStorefrontSearchConsoleVerification.
Endpoint de escrituraPUT /api/me/tenants/:documentId/search-console en src/api/tenant/routes/custom-tenant.ts.
Policiesglobal::require-auth y require-target-tenant-feature("search-console.manage").
Controller y serviceupdateManagedTenantSearchConsole 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.

CambioDetalle
Bloque Search ConsoleEn tenant-detail-page, el estado vacio muestra Definir; el estado activo muestra token y acciones Modificar/Quitar.
Servicio de escrituraTenantsService.updateSearchConsoleVerification(documentId, token) llama PUT /api/me/tenants/:id/search-console.
Copy de publicacionEl host donde se publicara el archivo se muestra solo cuando esta publicado y solo para hosts reales; queda oculto en localhost/dev.
Gate por featurecanManageSearchConsole 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.
CompatibilidadLas URLs viejas /settings/tenants/... redirigen a las nuevas.
SimplificacionSe elimina tenants-management-page porque el selector superior ya lista y cambia empresas.

Flujo: servir el archivo de verificacion

servir-archivo-verificacion.mmdmermaid
El diagrama se renderiza al cargar la pagina.

Flujo: canonical y 301

canonical-redirect.mmdmermaid
El diagrama se renderiza al cargar la pagina.

Flujo: sitemap y robots por host canonico

sitemap-robots-canonicos.mmdmermaid
El diagrama se renderiza al cargar la pagina.

Flujo: permiso RBAC para escribir el token

rbac-search-console.mmdmermaid
El diagrama se renderiza al cargar la pagina.

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.

setup-search-console.mmdmermaid
El diagrama se renderiza al cargar la pagina.
  1. 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.

  2. Elige Archivo HTML

    Google entregara un archivo del tipo google1a2b3c4d5e6f.html.

  3. Pega el archivo en Backoffice

    Con la empresa correcta seleccionada arriba, entra a Empresa -> Search Console -> Definir y pega el nombre del archivo.

  4. Espera publicacion

    La tienda servira automaticamente el archivo desde su dominio.

  5. Verifica en Search Console

    Vuelve a Google Search Console y presiona Verificar.

  6. Envia el sitemap

    Cuando la propiedad este verificada, envia https://<tu-dominio-principal>/sitemap.xml.

Modelo RBAC

ElementoValor
Featuresearch-console.manage, mostrado como Search Console Manage.
Roles baseSolo admin lo recibe mediante DEFAULT_ROLE_FEATURE_MATRIX.
Roles customSe puede activar desde /admin/roles/:id/edit porque el editor consume el catalogo de features.
Backend enforcementPUT /me/tenants/:id/search-console valida require-target-tenant-feature contra el tenant objetivo.
Frontend visibilityEl bloque se muestra si hasFeature("search-console.manage") para la empresa activa.
BootstrapStrapi 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

tenant.searchConsoleVerification.jsonjson
[
  {
    "provider": "google",
    "token": "google1a2b3c4d5e6f.html"
  }
]
ContratoForma
Strapi tenantsearchConsoleVerification es json y guarda una lista de provider/token. Hoy la UI escribe un token Google; la forma admite provider bing a futuro.
Lectura toleranteEntradas malformadas se descartan tanto en Strapi como en store.
Store contextcontext.siteVerifications expone readonly { provider: "google"|"bing"; token: string }[] | null.
Backoffice escrituraPUT /api/me/tenants/:documentId/search-console con body { data: { searchConsoleVerification: <token|null> } }.

Commits por repo

RepoCommitContenido
neges-storeb3d7742Fase 1 sitemap/robots canonicos, Fase 2 archivos de verificacion, Fase 3 redirect 301 y kill-switch.
neges-strapiab28d5fCampo searchConsoleVerification y exposicion en buildTenantPayload por ambos caminos de resolucion.
neges-strapic455fb5Feature RBAC search-console.manage y endpoint dedicado feature-gated, fuera del update general.
neges-backoffice2500267Bloque Search Console self-service en la pagina de empresa.
neges-backofficea4d519dRefinamiento de guards, host de publicacion y copy.
neges-backofficeb5f0e3bSimplifica copy del host de publicacion y lo oculta en local/dev.
neges-backofficeb7b0cc8Gate del bloque por feature search-console.manage y endpoint dedicado.
neges-backoffice9181f2aAjustes 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

RepoArchivos clave
neges-storesrc/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-strapisrc/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-backofficesrc/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 sesionsearch-console-tenant, indexado en MEMORY.md.

Recursos relacionados

¿Te fue útil este contenido?

Tu feedback nos ayuda a mejorar la documentación de Neges.

Soporte humano

¿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.

contacto@neges.clLun a Vie · 9 a 18 hrs
+56 9 0000 0000WhatsApp Business