Hreflang no Magento 2: SEO multi-idioma do jeito certo
O Magento 2 lida bem com vários idiomas no lado do conteúdo. Cada store view tem seu próprio locale, traduções, nomes de produto, URL keys e conteúdo CMS. O que ele não faz de fábrica é avisar os buscadores que as versões em inglês, alemão e português de uma página pertencem ao mesmo conjunto. Esse é o trabalho das anotações hreflang, e o Magento 2.4 não as gera.
Este artigo mostra como estruturar uma loja Magento multi-idioma para busca, como o hreflang funciona e como gerar links alternativos corretos para produtos, categorias e páginas CMS.
Primeiro, a estrutura das lojas
O hreflang só funciona se cada versão de idioma tiver a própria URL. No Magento há três jeitos comuns de conseguir isso:
- Store code na URL, como
example.com/en/eexample.com/de/, ativado com Add Store Code to Urls (web/url/use_store) ou com uma base URL separada por store view. - Subdomínios, como
en.example.com, com uma base URL por store view. - Domínios separados por país, cada um mapeado para um website ou store view na configuração do servidor web.
Qualquer um deles funciona para hreflang. O que não funciona é trocar o idioma por cookie na mesma URL. Os crawlers não mantêm o cookie da loja entre requisições, então só enxergam a store view padrão. Garanta que cada store view seja acessível só pela URL. As escolhas de estrutura por trás de websites, stores e store views estão em configuração de multi store e multi website.
Como o hreflang funciona
Cada página lista todas as suas versões de idioma, incluindo ela mesma, como elementos link no head:
<link rel="alternate" hreflang="en" href="https://example.com/en/blue-shirt.html"/>
<link rel="alternate" hreflang="de" href="https://example.com/de/blaues-hemd.html"/>
<link rel="alternate" hreflang="pt-BR" href="https://example.com/br/camisa-azul.html"/>
<link rel="alternate" hreflang="x-default" href="https://example.com/en/blue-shirt.html"/>
As regras que importam:
- As anotações precisam ser recíprocas. Se a página em inglês aponta para a alemã, a alemã precisa apontar de volta. Sem os links de retorno, os buscadores ignoram o par.
- Cada URL precisa ser a URL canonical daquela versão. Apontar o hreflang para uma URL que redireciona ou que tem canonical para outro lugar quebra o conjunto.
- Os códigos de idioma são ISO 639-1, com uma região ISO 3166-1 opcional, como
pt-BR. Os locales do Magento usam underscore (pt_BR), então precisam de conversão. - O
x-defaultmarca a versão para usuários que não batem com nenhum idioma listado. Normalmente é a loja do idioma principal ou uma página de seleção de idioma.
Achando a URL equivalente em cada store view
Para produtos e categorias, a URL em outra store view vem do URL rewrite dela naquela loja. Produtos podem ter URL keys diferentes por store view, então não dá para só trocar a base URL. O serviço URL finder busca o rewrite de uma entidade numa determinada loja.
<?php
declare(strict_types=1);
namespace Vendor\Hreflang\Model;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Store\Api\Data\StoreInterface;
use Magento\Store\Model\ScopeInterface;
use Magento\Store\Model\StoreManagerInterface;
use Magento\UrlRewrite\Model\UrlFinderInterface;
use Magento\UrlRewrite\Service\V1\Data\UrlRewrite;
class AlternateUrls
{
public function __construct(
private readonly UrlFinderInterface $urlFinder,
private readonly StoreManagerInterface $storeManager,
private readonly ScopeConfigInterface $scopeConfig
) {
}
/**
* @return array<string, string>
*/
public function forEntity(string $entityType, int $entityId): array
{
$current = $this->storeManager->getStore();
$links = [];
foreach ($this->storeManager->getStores() as $store) {
if (!$store->getIsActive() || (int) $store->getWebsiteId() !== (int) $current->getWebsiteId()) {
continue;
}
$path = $this->findRequestPath($entityType, $entityId, (int) $store->getId());
if ($path === null) {
continue;
}
$links[$this->getHreflangCode($store)] = $store->getBaseUrl() . $path;
}
return $links;
}
private function findRequestPath(string $entityType, int $entityId, int $storeId): ?string
{
$rewrites = $this->urlFinder->findAllByData([
UrlRewrite::ENTITY_TYPE => $entityType,
UrlRewrite::ENTITY_ID => $entityId,
UrlRewrite::STORE_ID => $storeId,
UrlRewrite::REDIRECT_TYPE => 0,
]);
foreach ($rewrites as $rewrite) {
if (empty($rewrite->getMetadata())) {
return $rewrite->getRequestPath();
}
}
return null;
}
private function getHreflangCode(StoreInterface $store): string
{
$locale = (string) $this->scopeConfig->getValue(
'general/locale/code',
ScopeInterface::SCOPE_STORE,
$store->getId()
);
return str_replace('_', '-', $locale);
}
}
Algumas notas sobre esse código:
- Rewrites com metadata são as variantes de URL de produto com caminho de categoria. Ignorá-los dá a URL curta do produto, que também deve ser a canonical.
REDIRECT_TYPE => 0exclui as entradas 301 que sobram de mudanças de URL key.- Se um produto não está atribuído a um website, ou está desativado numa store view, não haverá página utilizável ali. Checar só o rewrite não basta para produtos desativados, porque os rewrites podem continuar existindo. Carregue o status do produto por loja ou filtre por uma product collection antes de montar a lista.
- O exemplo limita os links ao website atual. Se seus idiomas ficam em websites diferentes, remova essa checagem, mas garanta que o produto exista em cada um.
Para os tipos de entidade, use product, category e cms-page. Os dois primeiros são definidos como constantes em ProductUrlRewriteGenerator e CategoryUrlRewriteGenerator. Quando dois locales mapeiam para o mesmo código, por exemplo duas store views em inglês para EUA e Reino Unido, use locales com região como en_US e en_GB para os códigos continuarem únicos.
Renderizando os links
Adicione um block ao container head.additional nos layouts de catalog_product_view, catalog_category_view e cms_page_view. O block recebe a entidade atual, chama o serviço e imprime um link por entrada, mais o x-default. Escape toda URL com $escaper->escapeUrl().
Esses links fazem parte da página em cache, e o cache de página é por store view e por URL, então isso está correto. Quando uma URL key muda numa store view, os alternates nas outras store views mudam também. Para produtos e categorias isso já é tratado, porque salvar a entidade limpa a cache tag dela em todas as store views. Para páginas CMS ligadas por um mapeamento próprio, a mudança numa página não limpa as irmãs, então adicione ao block as cache tags de todas as páginas do grupo ou limpe-as num observer de save.
Páginas CMS são a parte difícil
Produtos e categorias são a mesma entidade em todas as store views, com valores traduzidos. Páginas CMS não são. Uma loja multi-idioma típica tem uma página CMS por idioma, cada uma uma entidade separada com ID e identifier próprios, como about-us e ueber-uns. O Magento não tem nenhuma ligação nativa entre elas.
A solução mais limpa é um campo de mapeamento. Adicione uma coluna como hreflang_group em cms_page por declarative schema, exponha no formulário da página CMS e dê o mesmo valor de grupo às versões traduzidas. O block de hreflang então encontra todas as páginas ativas com o mesmo grupo e monta os links a partir dos identifiers e das atribuições de loja. Como adicionar a coluna está em declarative schema e data patches.
Uma página CMS atribuída a "All Store Views" com o mesmo conteúdo em todos os idiomas não deve ter hreflang nenhum, já que na prática é uma página só.
Hreflang no sitemap XML
Em vez de links no head, você pode listar os alternates dentro do sitemap XML com elementos xhtml:link por URL. Isso deixa o HTML mais leve e é mais fácil de gerar para catálogos grandes num lote só. O gerador de sitemap do Magento não faz isso de fábrica, então é preciso estender o model do sitemap ou gerar um sitemap separado. Use um método só, head ou sitemap, e não os dois, para evitar sinais conflitantes quando um deles estiver desatualizado.
Erros comuns
- Hreflang apontando para a home do outro idioma em vez da página equivalente.
- Usar o store code como código de idioma, como
hreflang="br", que não é um código válido para português. - Incluir store views desativadas ou ainda em construção.
- Links para URLs de produto com caminho de categoria quando a canonical aponta para a URL curta.
- Conteúdo traduzido só em parte, com descrições de produto em inglês na loja alemã. O hreflang não resolve conteúdo fraco ou duplicado.
Depois do release, confira os relatórios de segmentação internacional e faça crawl de uma amostra de páginas para confirmar a reciprocidade. O hreflang é um item da lista maior em checklist de SEO técnico, e a tradução em si está em traduções no Magento 2.
FAQ
O Magento 2 adiciona tags hreflang automaticamente?
Não. O Magento 2.4 não gera tags hreflang. Você precisa de um módulo próprio ou uma extensão que monte os links alternativos para produtos, categorias e páginas CMS.
Qual código de idioma devo usar numa store view do Magento?
Pegue o locale da store view em general/locale/code e troque o underscore por hífen, então pt_BR vira pt-BR. Garanta que cada store view do conjunto tenha um código único.
Como ligo páginas CMS traduzidas para o hreflang?
O Magento não tem ligação entre páginas CMS de idiomas diferentes. Adicione um campo de grupo compartilhado à página CMS e use-o para achar as páginas equivalentes.
Posso trocar o idioma do Magento por cookie e ainda usar hreflang?
Não. Cada versão de idioma precisa da própria URL. Use store codes na URL, subdomínios ou domínios separados.
Atendo como freelancer em Magento e Adobe Commerce, incluindo configurações multi-idioma e multi store. Se você precisa de ajuda com isso, fale comigo.
Artigos relacionados
Core Web Vitals no Magento 2: como corrigir LCP, INP e CLS
Onde lojas Magento 2 com Luma perdem nos Core Web Vitals e como corrigir LCP, INP e CLS: resposta do servidor, CSS, fontes, imagens, galeria e scripts de terceiros.
Migração do Magento 1 para o Magento 2: dados, SEO e plano de cutover
Plano prático de migração do Magento 1 para o 2.4.8: Data Migration Tool, data e delta, URL rewrites e redirects 301, ensaios e checklist de cutover.
Checklist de SEO técnico do Magento 2 para desenvolvedores
Checklist de SEO técnico do Magento 2: URLs e rewrites, canonicals, layered navigation, robots.txt, sitemap, status codes, HTTPS e conferências pós-deploy.
Guia de multi store e multi website no Magento 2
Como planejar e configurar multi website, multi store e store views no Magento 2: o que cada escopo controla, roteamento com Nginx, config por escopo e tudo no código.