SEO y metadata del head¶
MageObsidian completa la metadata del head que Magento deja vacÃa: una URL canónica en cada página, propiedades de Open Graph y Twitter Card, una meta description derivada de la propia página, las directivas modernas de robots y un Web App Manifest. Todo se renderiza en el servidor dentro del HTML cacheado —sin JavaScript adicional, sin nada calculado en el navegador.
La metadata vive en MageObsidian_Storefront y se escribe a través del propio Page\Config de Magento, asà que el escapeo y el render los hace el core. Cada pieza es un interruptor: una tienda que ya usa una extensión de SEO apaga la mitad que se duplica.
Nota de actualización: el head ahora incluye el asset collection de la página¶
Hasta esta versión el template raÃz del tema imprimÃa solo headContent y headAdditional. Nunca imprimÃa headAssets, que es lo único que renderiza el page asset collection de Magento.
Todo lo que pasa por Page\Config::addPageAsset() / addRemotePageAsset() se generaba, se guardaba… y se descartaba en silencio. Eso incluÃa:
- la canónica que el core emite en páginas de catálogo (con
catalog/seo/*_canonical_tagactivado), - los
<link rel="icon">/<link rel="shortcut icon">nativos, - el
<link rel="alternate">del RSS, - y lo que el merchant hubiera configurado en Design → HTML Head → Scripts and Style Sheets.
root.phtml ahora lo imprime. Es un arreglo, pero cambia lo que emite en su head una tienda existente, asà que conviene planificar la actualización sabiéndolo: medido en el demo de este proyecto, agrega cuatro etiquetas y entre 448 y 530 bytes por página. Ni RequireJS, ni jQuery, ni CSS de Luma: esos pipelines ya están excluidos de los temas Obsidian, asà que el collection simplemente no los contiene.
Lo único que conviene revisar antes de desplegar es el último punto de esa lista: si design/head/includes tiene algo cargado, ahora se está sirviendo y antes no. Lee la sección Head includes antes de dar por hecho que eso sale gratis.
URL canónica¶
Magento solo emite una canónica en páginas de catálogo, y solo con los flags de SEO de catálogo activados. Las páginas CMS, la home y los resultados de búsqueda no reciben ninguna. MageObsidian la emite en toda página que no tenga una ya.
Una canónica puesta por el core o por otra extensión nunca se sobrescribe. El módulo revisa primero el page asset collection; si ya hay una canónica reclamada, no escribe nada.
Parámetros de query: una lista de permitidos¶
Todo lo que no está en la lista se descarta, parámetros de tracking incluidos. El valor por defecto es p,q:
pse conserva porque un listado paginado tiene que apuntarse a sà mismo. Si la página 2 canonicaliza hacia la 1, los productos a los que solo se llega desde la página 2 salen del Ãndice.qse conserva porque sin él todos los resultados de búsqueda canonicalizan hacia la misma URL vacÃa.
Se usa una lista de permitidos y no una de bloqueados porque una lista de bloqueados envejece: hoy es utm_*, gclid y fbclid; mañana es otra cosa.
También se descarta el parámetro de paginación cuando vale 1, y cualquier parámetro permitido con valor vacÃo, asà que ?p=1 nunca genera una segunda URL para la primera página.
Sufijo de URL y la barra final¶
El path se normaliza sin su barra final, porque Magento responde 200 tanto en /about-us como en /about-us/: dejar las dos significarÃa dos URLs indexables para una misma página.
La excepción es una tienda cuyo catalog/seo/category_url_suffix o catalog/seo/product_url_suffix sea /. Ahà la barra es parte de la dirección, asà que el path se deja tal cual.
Configuración¶
| Ajuste | Ruta | Valor por defecto |
|---|---|---|
| Emit a Canonical URL | mage_obsidian/seo/canonical_enabled |
1 |
| Query Parameters Kept in the Canonical | mage_obsidian/seo/canonical_query_params |
p,q |
Stores → Configuration → MageObsidian → SEO.
Open Graph y Twitter Card¶
Con la metadata social activada, el head lleva lo siguiente, derivado del tÃtulo, la descripción y las imágenes de la propia página:
| Propiedad | Origen |
|---|---|
og:type |
product en una ficha de producto, website en el resto |
og:site_name |
nombre de la tienda |
og:locale |
general/locale/code |
og:title |
el tÃtulo de la página |
og:description |
la meta description de la página |
og:url |
la canónica resuelta —incluida una puesta por el core |
og:image |
la imagen de la entidad, luego la imagen social de respaldo, luego el logo de la tienda |
twitter:card |
summary_large_image |
twitter:site |
la cuenta configurada; se omite si está vacÃa |
twitter:title / twitter:description / twitter:image |
igual que arriba |
og:url usa deliberadamente la canónica resuelta y no la propia del módulo: si el core canonicaliza una ficha de producto hacia una URL, la URL que se comparte tiene que ser esa misma.
Complementa, no duplica¶
En una ficha de producto, MageObsidian_Catalog ya emite su propio bloque de Open Graph (opengraph.general, desde el view model ProductOpenGraph): og:type, og:title, og:url, og:image, og:description y el par product:price:*.
En lugar de fijar esa lista en el código, el módulo le pregunta al bloque qué emite. Un mapa en di.xml asocia el nombre de un bloque con el argumento de layout que contiene su view model, y al objeto se le consulta mediante getProperties(); lo que ese bloque reclame se le deja a él:
Si mañana ese bloque suma una propiedad, la detección se ajusta sola. Una tienda con una extensión de SEO de terceros agrega una lÃnea aquà y sus propiedades tampoco se duplican.
Configuración¶
| Ajuste | Ruta | Valor por defecto |
|---|---|---|
| Emit Open Graph and Twitter Card Metadata | mage_obsidian/seo/social_meta_enabled |
1 |
| Fallback Share Image | mage_obsidian/seo/social_image |
vacÃo (cae al logo de la tienda) |
| Twitter Account | mage_obsidian/seo/twitter_site |
vacÃo (twitter:site se omite) |
La imagen social de respaldo deberÃa ser de 1200×630 o mayor.
Meta description¶
Magento repite la descripción por defecto de la tienda en cada página que no tiene una propia. Con el fallback activado, el módulo la reemplaza por algo referido a la página real:
- gana la meta description propia de la entidad;
- si no la hay, se resume su contenido: se quita el HTML (
<script>y<style>con su contenido, y también las directivas{{...}}de Page Builder), se colapsan los espacios y se corta en un borde de palabra alrededor de los 160 caracteres; - si la página ya trae algo especÃfico —otra extensión, un handle de layout—, no se toca.
El módulo solo interviene cuando la página está repitiendo el valor por defecto de la tienda, y la comparación se hace sobre el texto normalizado, asà que el escapeo HTML que aplica setMetadata() no la invalida.
| Ajuste | Ruta | Valor por defecto |
|---|---|---|
| Derive the Meta Description from the Page | mage_obsidian/seo/meta_description_fallback |
1 |
Meta robots¶
Magento escribe INDEX,FOLLOW y ahà se detiene. El módulo agrega las tres directivas que controlan cuánto puede mostrar un buscador de la página:
Dos reglas lo mantienen seguro:
- Una página
NOINDEXno se toca nunca. La extensión se detiene apenas ve la directiva, asà que una página que debe quedar fuera del Ãndice permanece exactamente como la escribió Magento. - Solo escribe cuando realmente agrega algo. Una directiva ya presente no se repite, y cuando no hay nada que sumar el valor queda intacto.
| Ajuste | Ruta | Valor por defecto |
|---|---|---|
| Extra Robots Directives | mage_obsidian/seo/robots_directives |
max-image-preview:large,max-snippet:-1,max-video-preview:-1 |
VacÃa el campo para dejar el meta robots exactamente como lo escribió Magento.
Web App Manifest¶
El manifest se deriva de la configuración de la tienda —nombre, base URL, logo, favicon, colores—, asà que lo sirve un controller en vez de ser un archivo estático, y varÃa por vista de tienda.
Endpoint: /mage-obsidian-storefront/manifest/, enlazado desde el head con <link rel="manifest">.
Detalles que conviene conocer:
- El media type es
application/manifest+json, noapplication/json. - Es cacheable por un dÃa para el navegador, la CDN y Varnish. El full page cache de Magento no lo alcanza —solo cubre resultados
Result\Page—, asà que acá el mecanismo son las cabeceras HTTP. iconsaparece en cuanto haya un logo o un favicon configurado; sin ninguno de los dos, la propiedad se omite en lugar de emitirse vacÃa.- Apagado, el endpoint responde 404, no un manifest vacÃo, y ese
404no se anuncia como cacheable por un dÃa.
| Ajuste | Ruta | Valor por defecto |
|---|---|---|
| Serve a Web App Manifest | mage_obsidian/seo/manifest_enabled |
1 |
| Manifest Display Mode | mage_obsidian/seo/manifest_display |
standalone |
| Manifest Theme Colour | mage_obsidian/seo/manifest_theme_color |
#ffffff |
| Manifest Background Colour | mage_obsidian/seo/manifest_background_color |
#ffffff |
Head includes: nativo de Magento, y render-blocking¶
Design → HTML Head → Scripts and Style Sheets (design/head/includes) es una capacidad nativa de Magento. Lo que se pegue ahà se inserta tal cual en el <head>, en todas las páginas de la vista de tienda.
Todo lo que va en ese campo es render-blocking¶
Esta es la parte que se subestima:
- un
<script src>sinasyncnideferfrena el parseo del HTML hasta que se descarga, se parsea y se ejecuta; - un
<link rel="stylesheet">bloquea el primer render hasta que la hoja llega.
En un stack que mide FCP y LCP en milisegundos, un solo tag de terceros en ese campo puede costar más que todo el resto de la página. Es el único lugar del admin donde un merchant puede deshacer el presupuesto entero de frontend con un pegado.
El framework no lo reescribe¶
Ese campo es contenido del merchant sobre un contrato nativo de Magento, y quien lo pega es responsable de lo que hace. MageObsidian no lo toca. Los dos flags de abajo vienen apagados, asà que recién instalado el comportamiento es exactamente el de Magento.
Existen dos flags opt-in para quien haya mirado su propio contenido y haya decidido:
| Ajuste | Ruta | Valor por defecto |
|---|---|---|
| Defer Scripts In Head Includes | mage_obsidian/head/includes_defer_scripts |
0 |
| Defer Stylesheets In Head Includes | mage_obsidian/head/includes_defer_styles |
0 |
Stores → Configuration → MageObsidian → Frontend → HTML Head Includes.
Diferir scripts, y cuándo no conviene¶
Con el flag activado, un <script src> externo recibe defer. Las etiquetas con async, defer o type="module" ya no son bloqueantes y se dejan intactas; un script inline no se toca nunca, porque no hay forma segura de diferirlo y puede ser justamente lo que tiene que correr primero.
No enciendas esto si el campo contiene un tag que necesita correr bloqueando. Los dos casos habituales viven en ese mismo campo:
- Scripts anti-flicker de A/B testing. Diferidos, provocan exactamente el parpadeo del contenido original que existen para evitar.
- Gestores de consentimiento. Diferidos, el banner llega después de la página —y después de los tags que debÃa condicionar.
La regla posicional¶
Un script externo se difiere solo si no hay ningún <script> inline después de él en el mismo campo. El snippet canónico de analytics es un par:
Un script diferido corre después de que se parsea el documento, asà que correrÃa después de esa llamada inline: lib todavÃa no existirÃa y la página se romperÃa. La regla es posicional, no global:
Esa comprobación solo ve este campo. Si un script inline de una plantilla, de otro módulo o de un widget de Page Builder depende de un script pegado aquÃ, el framework no tiene forma de saberlo. Ese es el lÃmite de la heurÃstica, y es la razón por la que el flag viene apagado.
Diferir hojas de estilo, y cuándo no conviene¶
Con el flag activado, un <link rel="stylesheet"> pasa a ser un preload que se promueve a hoja de estilo en cuanto llega, con un respaldo en <noscript>:
El cambio lo hace un único <script> con nonce emitido una sola vez al final, nunca un atributo onload inline: bajo una CSP aplicada el atributo se descarta y la hoja quedarÃa como un preload que no se aplica jamás. Escucha el evento load y usa DOMContentLoaded como respaldo, ambos con { once: true }.
La página pinta antes de que esa CSS se aplique. Si la hoja da estilo a algo sobre el fold, el comprador ve un destello de contenido sin estilo y la maqueta salta cuando la hoja aterriza, lo que penaliza el Cumulative Layout Shift. Enciéndelo solo cuando sepas que la hoja da estilo a contenido bajo el fold o es puramente cosmética. Es la misma razón por la que el tema difiere su propia hoja únicamente en las páginas que inlinearon critical CSS —ver Rendimiento.
Dos detalles mantienen honesta la conversión:
media="print"se saltea. Nunca bloqueó el render de pantalla, asà que convertirla no ganarÃa nada y sà costarÃa algo: tras el swap pasarÃa a aplicarse a la pantalla.media,integrity,crossorigin,referrerpolicy,idytitlese preservan tanto en el preload como en el<noscript>.integrityycrossoriginson válidos enrel=preload as=styley el navegador verifica el hash ahÃ, asà que Subresource Integrity sigue funcionando de punta a punta.
La válvula de escape¶
Cualquier etiqueta que lleve data-obsidian-blocking queda exactamente como se escribió, digan lo que digan los flags:
Todo lo demás del campo —<meta>, <link rel="preconnect">, comentarios HTML, texto suelto, scripts inline— sale byte por byte igual a como entró. El contenido nunca se parsea para volver a serializarlo: solo se reescribe en el lugar la lista de atributos de una etiqueta de apertura que coincida.
El consejo que de verdad aplica¶
Antes de encender cualquiera de los dos flags, lo correcto casi siempre es sacar ese script de head includes y cargarlo bien desde el tema, donde se puede ordenar, empaquetar, diferir a propósito y medir. Estos flags son control de daños para contenido que no controlas o no puedes mover, no un sustituto de cargar un script correctamente.
Superficies nativas que el tema conserva¶
Bajo un tema MageObsidian, el gestor de módulos del framework declara «salida deshabilitada» para todo módulo Magento_*, y ese gestor está cableado en las fuentes de archivos de layout y de page layout del core. El efecto no es «el tema no aporta nada a esa pantalla»: el archivo de layout del core desaparece por completo. Una ruta del core que ningún módulo MageObsidian re-declare responde 200 con un cuerpo vacÃo, o con el cromo del tema y nada dentro, sin que ningún log lo diga.
Por eso el escaparate re-declara las superficies del core que un visitante, un rastreador o un operador siguen alcanzando por URL. Cada una vive en el módulo dueño del área, conserva el bloque nativo y se renderiza con una plantilla Twig del tema.
| Superficie | Ruta o page layout | Declarada en | Qué sirve |
|---|---|---|---|
robots.txt |
robots_index_index |
module-storefront |
Las instrucciones configuradas en Diseño › Robots de buscadores, como text/plain, seguidas de las lÃneas de sitemap |
| Seguimiento de envÃos | shipping/tracking/popup |
module-sales |
Transportista, número y estado de cada seguimiento, y el contacto de la tienda cuando el transportista no responde. El correo de envÃo y la página de envÃo de la cuenta enlazan a él |
| Lista de feeds RSS | /rss |
module-storefront |
Los feeds activos, un enlace por cada uno. 404 con RSS desactivado |
| Compartir la wishlist con RSS | wishlist_email_rss |
module-wishlist |
La casilla «incluir enlace RSS» aparece solo con Feeds RSS › Wishlist activo, y un envÃo que pida el enlace nunca falla |
| Aterrizaje de «Login as Customer» | loginascustomer/login/index |
module-customer |
Un meta refresh a la cuenta del cliente, con un enlace manual para quien lo tenga deshabilitado. Sin script |
| Page layouts a ancho completo | cms-full-width, category-full-width, product-full-width |
theme-base, en Magento_PageBuilder/ (page_layout/*.xml para la estructura, layouts.xml para la etiqueta que reconocen el admin y el constructor de página) |
Las columnas de 1column o 2columns-left con la clase page-main--full-width en el área de contenido, que el tema estiliza sin márgenes laterales |
Los page layouts viven en el tema bajo Magento_PageBuilder/ a propósito. Las fuentes de tema no pasan por el filtro, y esa carpeta desaparece con el módulo, en el mismo momento en que el admin deja de ofrecer esos layouts.
Dos rutas del core no se conservan de forma deliberada, y el registro de paridad de storefront-verification anota lo que cada una responde bajo el tema: review/product/listAjax, reemplazada por la isla de reseñas y sin ningún enlace hacia ella, y /swagger, una herramienta de desarrollo que solo se sirve fuera del modo producción.
Cuando una página bajo el tema responde con un <main> vacÃo, sospecha antes que nada de un handle del core descartado. El registro de paridad lista cada handle y page layout del core junto con quién lo re-declara, y rechaza una entrada «fuera de alcance» que no diga qué responde la ruta.
Próximos pasos¶
- Datos estructurados (JSON-LD) — la mitad de schema.org de esta misma historia.
- Rendimiento — critical CSS, los flags nativos de build y Varnish.