Vou compartilhar um número que me manteve acordado à noite: 3.540. São 30 idiomas multiplicados por 118 páginas. Se tivéssemos lançado tudo isso simultaneamente no Deluxe Astrology, o Google teria indexado milhares de páginas finas, não traduzidas ou lixo de máquina. Nossas classificações teriam despencado. Em vez disso, construímos um sistema de bloqueio de tradução em dois níveis que implementa idiomas progressivamente, usa Claude Haiku para tradução em lote a aproximadamente $22 por idioma, e controla a qualidade com pontuação Winston AI. Tudo funciona em Next.js com middleware next-intl, URLs canônicas cientes de localidade e tags hreflang tanto no head HTML quanto nos sitemaps XML. Este é o detalhamento completo de como fizemos -- cada configuração de middleware, cada entrada de sitemap, cada cálculo de custo.

Índice

Tags Hreflang em Next.js: Como Colocamos 30 Idiomas em 118 Páginas

Por Que Tags Hreflang Ainda Importam em 2025

A detecção de idioma do Google melhorou. Vou dar o crédito a eles. Mas "melhor" não significa "resolvido". Se você está executando variantes regionais -- pense em pt-BR vs pt-PT, ou zh-CN vs zh-TW -- o Google ainda precisa de sinais explícitos. Sem hreflang, você verá suas páginas em português do Brasil canibalizando seu conteúdo direcionado a Portugal, e vice-versa.

Aqui está o que os dados nos dizem:

  • Mais de 60% dos sites multilíngues têm erros de configuração de hreflang (fonte: estudos Ahrefs sobre auditorias de SEO internacional)
  • A implementação adequada de hreflang pode aumentar as taxas de cliques em 20-30% em mercados direcionados dentro de 4-6 semanas
  • Sites sem hreflang experimentam rotações de classificação imprevisíveis entre versões de idioma, tornando o rastreamento de desempenho quase impossível

Para o Deluxe Astrology, estamos direcionando 30 idiomas com conteúdo distinto. Não variantes regionais -- idiomas reais e diferentes. São 30 públicos diferentes que precisam encontrar a versão certa nos resultados de busca. Hreflang não é opcional aqui. É a base.

O que a maioria dos guias perde: você precisa de hreflang tanto no <head> HTML quanto no seu sitemap XML. Não um ou outro. Os dois. O Google confirmou que processa hreflang de múltiplas fontes, e redundância aqui não é desperdício -- é seguro.

O Problema das 3.540 Páginas

Deixe-me detalhar a matemática que moldou toda a nossa arquitetura.

Deluxe Astrology tem:

  • 118 páginas (páginas de conteúdo principal)
  • 41 namespaces de tradução (agrupamentos lógicos de strings traduzíveis)
  • 39 rotas de API cientes de localidade
  • 30 idiomas direcionados

30 × 118 = 3.540 variantes totais de página.

Se lançássemos todas as 3.540 páginas no primeiro dia, aqui está o que aconteceria:

  1. A maioria das páginas conteria texto fallback em inglês com um caminho de URL em outro idioma. O Google vê isso como conteúdo fino/duplicado.
  2. O Googlebot queimaria orçamento de rastreamento indexando milhares de páginas de baixa qualidade.
  3. O sinal geral de qualidade do site despencaria, arrastando para baixo até as boas páginas em inglês.
  4. Usuários chegando em páginas não traduzidas teriam uma taxa de rejeição imediata.

Isso não é teórico. Já vi acontecer em sites de clientes que plugaram Weglot ou ferramentas similares e ativaram para 20 idiomas da noite para o dia. O tráfego caiu, não subiu.

A solução: não lance todos os idiomas de uma vez. Bloqueie-os.

Sistema de Bloqueio de Tradução em Dois Níveis

Dividimos nossos 30 idiomas em dois níveis com estratégias de lançamento fundamentalmente diferentes.

Nível 1: TRANSLATED_LOCALES

Estes são 15 idiomas com páginas principais totalmente traduzidas, revisadas manualmente por falantes nativos ou bilíngues verificados.

// config/locales.ts
export const TRANSLATED_LOCALES = [
  'en', 'es', 'fr', 'de', 'it', 'pt-BR', 'ja', 'ko',
  'zh-CN', 'zh-TW', 'ru', 'ar', 'hi', 'tr', 'nl'
] as const;

Estes 15 idiomas recebem:

  • Tags hreflang completas em todas as 118 páginas
  • Inclusão no sitemap XML
  • URLs canônicas indexáveis
  • Marcação de schema ciente de localidade

Nível 2: DYNAMIC_TRANSLATED_LOCALES

Os 15 idiomas restantes começam como espaços reservados apenas em inglês. Eles não são indexados. Eles não recebem entradas de hreflang. Eles não existem no sitemap.

export const DYNAMIC_TRANSLATED_LOCALES = [
  'pl', 'sv', 'da', 'fi', 'no', 'cs', 'ro', 'hu',
  'el', 'th', 'vi', 'id', 'ms', 'uk', 'bg'
] as const;

export const ALL_LOCALES = [
  ...TRANSLATED_LOCALES,
  ...DYNAMIC_TRANSLATED_LOCALES
] as const;

Quando um idioma do Nível 2 completa o pipeline de tradução -- tradução em lote Claude Haiku, porta de qualidade Winston AI, revisão humana opcional -- ele se gradua para o Nível 1. As entradas hreflang, inclusão de sitemap e diretivas de indexação são atualizadas automaticamente.

// utils/locale-status.ts
export function isLocaleReady(locale: string): boolean {
  // Verificar se todos os namespaces necessários têm traduções
  // com pontuações Winston AI >= 95%
  const status = getTranslationStatus(locale);
  return status.completedNamespaces >= REQUIRED_NAMESPACES
    && status.minQualityScore >= 0.95;
}

export function getIndexableLocales(): string[] {
  return ALL_LOCALES.filter(isLocaleReady);
}

Essa é a percepção chave: sua implementação de hreflang precisa ser dinâmica. Não pode ser uma lista estática codificada no tempo de construção (bem, pode se você reconstruir quando locais se graduar, que é o que fazemos com ISR).

Tags Hreflang em Next.js: Como Colocamos 30 Idiomas em 118 Páginas - arquitetura

Configuração do Middleware Next-intl

O middleware é onde a detecção de localidade, roteamento e a lógica de bloqueio convergem. Aqui está nosso middleware.ts real:

// middleware.ts
import createMiddleware from 'next-intl/middleware';
import { NextRequest, NextResponse } from 'next/server';
import { ALL_LOCALES, TRANSLATED_LOCALES } from './config/locales';

const intlMiddleware = createMiddleware({
  locales: ALL_LOCALES,
  defaultLocale: 'en',
  localePrefix: 'always',
  localeDetection: true
});

export default function middleware(request: NextRequest) {
  const pathname = request.nextUrl.pathname;
  
  // Extrair localidade do caminho
  const pathnameLocale = ALL_LOCALES.find(
    (locale) => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`
  );

  // Se a localidade está no Nível 2 e ainda não está pronta,
  // servir conteúdo mas adicionar header noindex
  if (
    pathnameLocale &&
    !TRANSLATED_LOCALES.includes(pathnameLocale) &&
    !isLocaleReady(pathnameLocale)
  ) {
    const response = intlMiddleware(request);
    response.headers.set('X-Robots-Tag', 'noindex, nofollow');
    return response;
  }

  return intlMiddleware(request);
}

export const config = {
  matcher: ['/((?!api|_next|_vercel|.*\\..*).*)'] 
};

Algumas coisas a notar aqui:

  1. localePrefix: 'always' -- Toda URL recebe um prefixo de localidade. /en/horoscope, /de/horoskop, etc. Sem ambiguidade. Isso é crítico para hreflang porque toda URL alternada deve ser distinta e previsível.

  2. Nível 2 noindex -- Locais não traduzidas ainda renderizam (usuários dessas regiões ainda podem navegar), mas recebem um header noindex. O Google não desperdiçará orçamento de rastreamento com elas.

  3. O matcher -- Excluímos rotas de API, elementos internos do Next.js e arquivos estáticos. As 39 rotas de API cientes de localidade têm seu próprio tratamento de localidade.

Se você está construindo algo similar, escrevemos mais sobre nossa abordagem de desenvolvimento Next.js e como o middleware se encaixa na arquitetura.

Implementação de Hreflang no Head HTML

Next.js 14+ com o App Router nos dá a função generateMetadata. É aqui que as tags hreflang vão no <head> HTML.

// app/[locale]/[...slug]/page.tsx
import { getIndexableLocales } from '@/utils/locale-status';
import { getLocalizedSlug } from '@/utils/slugs';

type Props = {
  params: { locale: string; slug: string[] };
};

export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const { locale, slug } = params;
  const baseUrl = 'https://deluxeastrology.com';
  const pagePath = slug ? `/${slug.join('/')}` : '';
  const indexableLocales = getIndexableLocales();

  // Construir alternativas de idioma -- apenas para localidades indexáveis
  const languages: Record<string, string> = {};
  
  for (const loc of indexableLocales) {
    const localizedSlug = await getLocalizedSlug(pagePath, loc);
    languages[loc] = `${baseUrl}/${loc}${localizedSlug}`;
  }

  // x-default aponta para inglês
  languages['x-default'] = `${baseUrl}/en${pagePath}`;

  return {
    title: await getLocalizedTitle(pagePath, locale),
    alternates: {
      canonical: `${baseUrl}/${locale}${pagePath}`,
      languages
    }
  };
}

Isso gera HTML assim:

<link rel="canonical" href="https://deluxeastrology.com/de/horoskop" />
<link rel="alternate" hreflang="en" href="https://deluxeastrology.com/en/horoscope" />
<link rel="alternate" hreflang="de" href="https://deluxeastrology.com/de/horoskop" />
<link rel="alternate" hreflang="fr" href="https://deluxeastrology.com/fr/horoscope" />
<!-- ... mais 12 locais indexáveis ... -->
<link rel="alternate" hreflang="x-default" href="https://deluxeastrology.com/en/horoscope" />

Dois detalhes críticos:

  1. A URL canônica é específica da localidade. A página em alemão tem canônico a URL em alemão, não a em inglês. Cada versão de idioma é sua própria página canônica.
  2. x-default está sempre presente. Aponta para inglês. Se o Google não conseguir combinar o idioma de um usuário a nenhuma de suas entradas hreflang, x-default é o fallback.

Geração de Sitemap com Entradas Hreflang

Hreflang no <head> HTML é necessário mas não suficiente. Para um site com 3.540 variantes de página potenciais, você também precisa de hreflang no seu sitemap XML. Aqui está o porquê: o Google pode descobrir relacionamentos de hreflang do sitemap sem rastrear primeiro cada página.

// app/sitemap.ts
import { MetadataRoute } from 'next';
import { getIndexableLocales } from '@/utils/locale-status';
import { getAllPages } from '@/utils/pages';
import { getLocalizedSlug } from '@/utils/slugs';

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
  const baseUrl = 'https://deluxeastrology.com';
  const indexableLocales = getIndexableLocales();
  const pages = await getAllPages(); // Retorna 118 definições de página

  const entries: MetadataRoute.Sitemap = [];

  for (const page of pages) {
    for (const locale of indexableLocales) {
      const localizedSlug = await getLocalizedSlug(page.path, locale);
      const url = `${baseUrl}/${locale}${localizedSlug}`;

      // Construir alternadas para esta página específica
      const alternates: Record<string, string> = {};
      for (const altLocale of indexableLocales) {
        const altSlug = await getLocalizedSlug(page.path, altLocale);
        alternates[altLocale] = `${baseUrl}/${altLocale}${altSlug}`;
      }
      alternates['x-default'] = `${baseUrl}/en${page.path}`;

      entries.push({
        url,
        lastModified: page.updatedAt,
        changeFrequency: page.changeFreq || 'weekly',
        priority: locale === 'en' ? 0.9 : 0.8,
        alternates: {
          languages: alternates
        }
      });
    }
  }

  return entries;
}

Isso gera XML assim:

<url>
  <loc>https://deluxeastrology.com/de/horoskop</loc>
  <lastmod>2025-01-15</lastmod>
  <xhtml:link rel="alternate" hreflang="en" href="https://deluxeastrology.com/en/horoscope"/>
  <xhtml:link rel="alternate" hreflang="de" href="https://deluxeastrology.com/de/horoskop"/>
  <xhtml:link rel="alternate" hreflang="fr" href="https://deluxeastrology.com/fr/horoscope"/>
  <xhtml:link rel="alternate" hreflang="x-default" href="https://deluxeastrology.com/en/horoscope"/>
</url>

Com 15 localidades indexáveis e 118 páginas, são 1.770 entradas de sitemap. Gerenciável. Quando todos os 30 idiomas estiverem prontos, serão 3.540. Ainda dentro do limite de 50.000 URLs de sitemap do Google, mas dividimos em sitemaps por localidade de qualquer forma para monitoramento mais limpo do Google Search Console.

Pipeline de Tradução: Claude Haiku + Winston AI

É aqui que a economia fica interessante. Precisávamos traduzir 118 páginas em 41 namespaces para 30 idiomas. Tradução humana profissional seria o padrão ouro, mas a matemática de orçamento é brutal.

O Pipeline

  1. Extrair -- Puxar todas as strings traduzíveis de 41 namespaces para JSON estruturado
  2. Traduzir -- Processar em lote através de Claude Haiku (modelo rápido e barato do Anthropic) com contexto sobre o domínio (astrologia), tom e público-alvo
  3. Porta de Qualidade -- Executar conteúdo traduzido através da detecção de conteúdo e pontuação de qualidade do Winston AI. Limite: 95%+ ou rejeitar.
  4. Revisão Humana -- Páginas de alto valor (página inicial, landing pages, páginas de dinheiro) recebem revisão manual por falantes nativos
  5. Graduação -- Uma vez que todos os namespaces passam nas portas de qualidade, a localidade se move de DYNAMIC_TRANSLATED_LOCALES para TRANSLATED_LOCALES
// scripts/translate-locale.ts
async function translateLocale(targetLocale: string) {
  const namespaces = await getNamespaces(); // 41 namespaces
  
  for (const ns of namespaces) {
    const sourceStrings = await loadNamespace('en', ns);
    
    const translated = await claude.messages.create({
      model: 'claude-3-haiku-20240307',
      max_tokens: 4096,
      system: `Você é um tradutor profissional especializado em conteúdo de astrologia. 
               Traduza do inglês para ${getLanguageName(targetLocale)}. 
               Mantenha a precisão da terminologia astrológica. 
               Preserve todas as variáveis de interpolação como {name} e {date}.`,
      messages: [{
        role: 'user',
        content: `Traduza estes pares chave-valor JSON. Retorne apenas JSON válido:\n${JSON.stringify(sourceStrings, null, 2)}`
      }]
    });

    const qualityScore = await winstonAI.analyze(translated.content);
    
    if (qualityScore >= 0.95) {
      await saveNamespace(targetLocale, ns, translated.content);
    } else {
      await flagForReview(targetLocale, ns, translated.content, qualityScore);
    }
  }
}

O custo por idioma com Claude Haiku funciona em aproximadamente $22 para todas as 118 páginas em 41 namespaces. Isso é principalmente custos de token -- Haiku é incrivelmente barato a $0,25 por milhão de tokens de entrada e $1,25 por milhão de tokens de saída (preços de 2025).

Comparação de Custos: Nossa Abordagem vs Alternativas

Esta é a tabela que convenceu o time do Deluxe Astrology:

Abordagem Custo para 30 Idiomas Custo Contínuo Qualidade Tempo para Lançar
Claude Haiku + Winston AI ~$660 total ($22/idioma) $0 (uma vez) Porta de qualidade 95%+, revisão humana para páginas-chave 2-3 semanas em progressão
Weglot $0 setup $699/mês ($8.388/ano) Tradução de máquina, editável Instantâneo mas arriscado
Tradutores Profissionais $150K-$300K ($5K-10K/idioma) $2K-5K/idioma para atualizações Qualidade mais alta 3-6 meses
DeepL API ~$400 total $0 (uma vez) Boa mas sem porta de qualidade 1-2 semanas
Google Translate API ~$300 total $0 (uma vez) Qualidade mais baixa para conteúdo de nicho 1 semana

Sejamos honestos: os $660 totais para tradução Claude Haiku de 30 idiomas é quase suspeitosamente barato. A pegadinha é que você precisa da porta de qualidade (Winston AI) e camada de revisão humana para torná-lo pronto para produção. Mesmo com esses custos considerados -- talvez $50-100 para chamadas de API do Winston AI e $500-1.000 para revisão humana de páginas de alto valor -- você ainda está abaixo de $2.000 totais. Compare isso com o $699/mês do Weglot. Você teria retorno sobre investimento em menos de 3 meses.

O verdadeiro assassino com Weglot e serviços similares: eles traduzem tudo de uma vez. Sem bloqueio. Sem controle de qualidade por página. Você ativa um switch e de repente o Google vê 3.540 páginas, muitas delas são traduções de máquina medíocres. Nossa abordagem permite que você seja cirúrgico sobre isso.

Falamos mais sobre como abordamos projetos assim em nossa página de preços -- o pipeline de tradução é um componente de uma arquitetura maior de desenvolvimento de CMS headless.

Marcação de Schema Ciente de Localidade

Este é o que pega quase todos de surpresa. Seus dados estruturados precisam corresponder ao idioma da página. Uma página em alemão com schema FAQ em inglês confunde o entendimento do Google sobre a página.

// utils/schema.ts
export function generateFAQSchema(
  faqs: Array<{ question: string; answer: string }>,
  locale: string
) {
  return {
    '@context': 'https://schema.org',
    '@type': 'FAQPage',
    'inLanguage': locale, // Crítico: deve corresponder à localidade da página
    'mainEntity': faqs.map((faq) => ({
      '@type': 'Question',
      'name': faq.question, // Deve estar no idioma alvo
      'acceptedAnswer': {
        '@type': 'Answer',
        'text': faq.answer // Deve estar no idioma alvo
      }
    }))
  };
}

Todo tipo de schema que suporta inLanguage deveria usá-lo. Para Deluxe Astrology, isso inclui:

  • FAQPage -- Perguntas e respostas no idioma alvo
  • Article -- inLanguage correspondendo à localidade
  • WebPage -- Propriedade inLanguage
  • BreadcrumbList -- Nomes de breadcrumb no idioma alvo

Não apenas traduza o conteúdo visível e esqueça dos dados estruturados. O Google lê os dois.

Erros Comuns Que Vão Destruir Suas Classificações

Faltando hreflang x-default

Vejo isso o tempo todo. Sites implementam hreflang para todos os seus idiomas mas esquecem x-default. Sem isso, o Google não tem fallback para usuários cujo idioma não corresponde a nenhuma de suas versões. Sempre inclua. Sempre aponte para seu idioma primário (geralmente inglês).

Localidade inconsistente em URL vs conteúdo

Se sua URL diz /fr/horoscope mas o conteúdo da página está em inglês porque a tradução não carregou ou fez fallback, o Google vai sinalizar isso como 404 suave ou conteúdo fino. Isso é exatamente por que construímos o sistema de bloqueio em dois níveis -- uma página não recebe uma URL em francês até que tenha conteúdo em francês.

Lançando todos os idiomas de uma vez

Já bati neste tambor antes, mas vale repetir. Lançar 30 idiomas simultaneamente é o erro mais comum em SEO internacional. Mesmo se suas traduções forem perfeitas, você está pedindo ao Google para rastrear, indexar e avaliar milhares de páginas novas da noite para o dia. Implemente em lotes de 3-5 idiomas. Monitore a indexação no GSC. Depois adicione mais.

Tags hreflang não recíprocas

Se a página A (inglês) aponta para a página B (alemão) via hreflang, a página B deve apontar de volta para a página A. Se este link recíproco está faltando, o Google ignora o hreflang inteiramente. Quando você está gerando estes dinamicamente (como fazemos), reciprocidade é automática. Mas se você está gerenciando manualmente, faça auditoria regularmente.

Faltando hreflang auto-referenciador

Toda página deve incluir a si mesma em seu próprio conjunto de hreflang. A página em alemão deve listar hreflang="de" apontando para si mesma. Isso é fácil de perder em implementações manuais.

Hreflang em apenas um lugar

Colocar hreflang apenas no <head> ou apenas no sitemap é um erro. Use ambos. Cinto e suspensório. O Google processa ambas as fontes, e se uma falhar em ser rastreada, a outra serve como backup.

Para projetos nesta escala, ter um time experiente ajuda a evitar estas armadilhas. Se você está planejando uma construção multilíngue, estamos felizes em discutir a abordagem.

FAQ

Preciso de tags hreflang se tenho apenas diferenças de idioma (não regional)?

Sim. Enquanto a detecção de idioma do Google melhorou em 2025, hreflang ainda é o sinal definitivo para dizer aos mecanismos de busca qual versão de idioma servir. Sem elas, você corre o risco de o Google mostrar sua página em inglês para usuários francófonos simplesmente porque a versão em inglês tem mais backlinks. Hreflang se torna ainda mais crítico quando você tem 10+ idiomas -- a probabilidade de canibalização entre idiomas aumenta dramaticamente com a escala.

Quantas entradas de hreflang são demais para uma única página?

O Google não publicou um limite oficial, mas testes práticos mostram que além de 50 variantes de idioma por página, você começa a ver diminuição de retorno e ocasionalmente problemas de análise. Para nossa configuração de 30 idiomas, cada página tem 31 entradas de hreflang (30 idiomas + x-default), o que está bem dentro da zona segura. Se você está lidando com 50+ combinações regionais e de idioma, considere usar apenas a abordagem de sitemap XML para manter o tamanho do <head> gerenciável.

Devo usar hreflang no head HTML, sitemap XML ou headers HTTP?

Para aplicações Next.js, use tanto HTML <head> quanto sitemap XML. Headers HTTP são principalmente úteis para recursos não-HTML como PDFs. A abordagem HTML <head> é processada em tempo de rastreamento e dá o sinal mais rápido. O sitemap funciona como um backup e ajuda o Google a descobrir páginas alternadas que ainda não rastreou. Não recomendamos confiar em apenas um método.

Qual é o custo de traduzir um site completo com IA em 2025?

Usando Claude Haiku, traduzimos 118 páginas em 41 namespaces para aproximadamente $22 por idioma. Para 30 idiomas, são cerca de $660 totais. Adicione porta de qualidade Winston AI em aproximadamente $50-100 para chamadas de API e revisão humana opcional para páginas de alto valor em $500-1.000, e seu custo total está abaixo de $2.000. Compare com Weglot em $699/mês ou serviços de tradução profissional em $5.000-10.000 por idioma.

Por que usar um sistema de bloqueio de tradução em dois níveis em vez de traduzir tudo de uma vez?

O Google trata conteúdo fino como um sinal negativo de qualidade que pode arrastrar para baixo seu domínio inteiro. Se você lançar 30 idiomas mas apenas 15 têm traduções de qualidade, esses 15 idiomas mal traduzidos criam aproximadamente 1.770 páginas de baixa qualidade. O sistema de dois níveis garante que apenas páginas atingindo um limite de qualidade 95%+ sejam indexadas. Idiomas se graduar de Nível 2 para Nível 1 conforme traduções passam em portas de qualidade, protegendo sua autoridade de domínio durante o rollout.

Como lidar com páginas não traduzidas para uma localidade que está parcialmente traduzida?

Para localidades onde alguns namespaces estão traduzidos mas outros não, fazemos fallback para conteúdo em inglês e adicionamos uma meta tag noindex via middleware. A URL ainda é resolvida (usuários podem acessá-la), mas o Google não vai indexar a página em idioma misto. Uma vez que todos os namespaces necessários passam nas portas de qualidade, a tag noindex é removida e entradas de hreflang são adicionadas. Isso previne traduções parciais de poluir seu índice.

Qual limite de pontuação de qualidade devo usar para traduções de IA?

Usamos Winston AI com um limite de pontuação de qualidade 95%+. Qualquer coisa abaixo fica sinalizada para revisão humana ou re-tradução com prompts ajustados. Na prática, Claude Haiku atinge 95%+ em cerca de 85% de lotes de namespace na primeira passagem. Os 15% restantes tipicamente falham devido a terminologia específica de domínio (termos astrológicos que não traduzem diretamente) ou estruturas de sentença complexas. Um limite de 90% deixaria passar phrasing notavelmente estranho.

Posso usar Astro em vez de Next.js para sites multilíngues com hreflang?

Absolutamente. Astro tem excelente suporte de i18n integrado desde Astro 4.0+, e seu modelo de geração estática na verdade simplifica a implementação de hreflang já que todas as URLs são conhecidas em tempo de construção. Construímos projetos multilíngues com ambos os frameworks. Para sites com conteúdo dinâmico pesado e rotas de API (como os 39 endpoints cientes de localidade do Deluxe Astrology), Next.js é o melhor ajuste. Para sites pesados em conteúdo com menos interatividade, desenvolvimento Astro pode ser mais rápido e performático. Os princípios de hreflang são idênticos independentemente do framework.