[🛠] Reduzir o tempo de build do blog multilíngue de 21 minutos para a faixa de 1 minuto
✨ Resumo do GPT-5.5
Registro de como rastreei pelo profile o build do Jekyll que passava de 20 minutos depois da expansão multilíngue, removi renderizações repetidas e scans do site inteiro, e reduzi o tempo até 1 minuto e 50 segundos.
Em um post anterior sobre a introdução multilíngue, adicionei ao blog uma estrutura operacional multilíngue.
No começo, isso me deixou bastante satisfeito.
ko, en, ja, zh-Hans, es, pt-BR, fr, id.
Tinha posts, menu, hreflang e visualizações compartilhadas. Por fora, parecia um blog multilíngue bem plausível.
Mas outro problema apareceu imediatamente.
O build demorava demais.
O primeiro production build ficou assim.
done in 1311.791 seconds
21 minutos e 52 segundos.
Esse não é um número que deveria sair de um blog simples. Se corrigir um post faz o build passar de 20 minutos, mais tarde o tempo esperando build fica maior do que o tempo escrevendo.
No começo, seria possível pensar simplesmente: “ficou lento porque agora os posts existem em 8 idiomas”.
Mas essa conclusão era apressada demais.
O número de páginas aumentou, sim. Mesmo assim, chegar à faixa de 20 minutos significava que havia mais alguma coisa. Então, desta vez, eu não corrigi no feeling. Liguei o jekyll build --profile.
Primeiro culpado: eu estava enfiando o JSON do calendário em todas as páginas
Primeiro olhei o tamanho de _site.
_site: 1.2G
HTML total: cerca de 840MB
840MB de HTML em um blog estático.
Não fazia sentido.
Ao abrir um post representativo, o motivo apareceu na hora. O calendário da sidebar estava colocando inline, em todas as páginas de post, o JSON com a lista completa de posts daquele idioma.
<script type="application/json" data-calendar-posts>
[
...
]
</script>
E não era só isso.
Ao criar a lista fallback do calendário, ele varria a lista inteira de posts de novo para cada data. Mesmo posts que não batiam com a condição geravam uma enorme saída de espaços em branco do Liquid. A tela visível era pequena, mas dentro do HTML havia espaços e listas repetidas enormes escondidas.
Isso não era um problema de funcionalidade. Era um problema de estrutura.
O calendário não precisava ser montado pelo servidor como HTML final em todas as páginas. O JS já conseguia desenhar o calendário dinamicamente. Então o servidor só precisava entregar a estrutura básica do mês atual e o caminho do arquivo de dados.
Por isso extraí os dados do calendário para arquivos JSON separados por idioma.
/assets/data/calendar-posts-ko.json
/assets/data/calendar-posts-en.json
/assets/data/calendar-posts-ja.json
/assets/data/calendar-posts-zh-Hans.json
/assets/data/calendar-posts-es.json
/assets/data/calendar-posts-pt-BR.json
/assets/data/calendar-posts-fr.json
/assets/data/calendar-posts-id.json
E deixei só isto na página.
data-calendar-posts-src="/assets/data/calendar-posts-en.json"
O resultado caiu imediatamente.
1311.791 segundos -> 745.273 segundos
Reduziu quase pela metade, mas ainda era longo.
Foi aí que entendi.
O calendário era um grande culpado, mas não era o único.
Segundo culpado: as estatísticas do menu estavam sendo calculadas 80 mil vezes
O profile seguinte foi ainda mais explícito.
_includes/sidebar-nav-stats.html 81120 calls 173.084s
_includes/masthead.html 2704 calls 319.860s
_includes/seo.html 2704 calls 119.985s
sitemap.xml 1 call 117.495s
O mais engraçado aí era sidebar-nav-stats.html.
Esse include é um pedacinho que coloca quantidade de posts e horário do post mais recente ao lado das categorias da sidebar.
Por exemplo, algo assim.
Daily Review (310) 1 days ago
Devlog (24) 2 days ago
Só que, toda vez que esse pedacinho era chamado, ele ordenava a lista inteira de posts de novo e filtrava tudo de novo.
Para cada item de menu de cada idioma.
Para cada página.
E mais uma vez na sidebar desktop e no menu mobile.
O resultado foi 81.120 chamadas.
Na verdade, esse valor não precisa ser recalculado em cada página. Para o mesmo idioma e o mesmo URL de menu, o resultado é idêntico. Então troquei para include_cached do jekyll-include-cache.
{% include_cached sidebar-nav-stats.html url=child.url lang=current_lang %}
Com isso, a contagem de chamadas mudou assim.
81120 calls -> 210 calls
173 segundos -> 0.4 segundo
Isso foi quase como corrigir um bug.
Terceiro culpado: eu varria o site inteiro para criar links de idioma
Ao adicionar a troca multilíngue, masthead, seo e sitemap receberam uma lógica deste tipo.
Esta URL de idioma existe de verdade?
A intenção estava certa.
Não dá para colocar no hreflang ou no menu de troca de idioma uma URL de tradução que não existe. Por isso, no começo, eu rodava por todas as páginas e por todos os documentos das collections para verificar a existência da URL.
O problema era fazer isso em todas as páginas.
2704 pages * scan de site.pages * scan das translated collections
Essa é uma estrutura que fica pior continuamente à medida que um site multilíngue cresce.
Então mudei o método.
As URLs de tradução deste blog já seguem uma regra.
/some/post/
/en/some/post/
/ja/some/post/
...
E as exceções podem ser mantidas em dados separados.
O post de retrospectiva desta sessão, que ainda estava com tradução pendente, entrou em _data/i18n_pending.yml.
entries:
- source_url: /devlog/github-pages-blog/github-pages-blog-english-version-lessons/
locales:
- en
- ja
- zh-Hans
- es
- pt-BR
- fr
- id
Assim, posts comuns são conectados pela regra de prefixo, e posts pendentes fazem fallback para a home do outro idioma. Não há scan do site inteiro.
O resultado foi grande.
masthead: 319.860 segundos -> 9.882 segundos
seo: 119.985 segundos -> 7.181 segundos
sitemap: 117.495 segundos -> 4.633 segundos
E o build final terminou assim.
done in 110.344 seconds
De 21 minutos e 52 segundos para 1 minuto e 50 segundos.
Ainda é difícil chamar isso de um blog rápido, mas pelo menos saí do estado de “o build dá tanto medo que não consigo escrever”.
O que exigiu cuidado enquanto eu corrigia
Olhando só para velocidade, esta otimização parece fácil.
Mas o que realmente exigia cuidado era o risco de quebrar funcionalidades.
Especialmente em um site multilíngue, dá para comemorar que o build ficou rápido e acabar criando problemas assim.
o link de troca de idioma vai para 404
hreflang aponta para URL inexistente
post com tradução pendente aparece como alternate para mecanismo de busca
About/botão de idioma somem de novo no mobile
calendário fica vazio
Por isso, no fim, rodei verificações automáticas junto com o navegador.
O que confirmei foi isto.
production build bem-sucedido
links de troca de idioma normais nos 8 idiomas em um post de viagem em inglês
hreflang normal para 8 idiomas + x-default
posts com tradução pendente fazem fallback para a home de outro idioma
About, 🇺🇸English e calendário aparecem normalmente no mobile
URLs multilíngues representativas retornam 200
verificação completa da estrutura renderizada de 2481 posts fonte
parsing do JSON do calendário confirmado
i18n post coverage errors: 0
Não li os 2481 posts um por um com olhos humanos.
Mas, pelo menos, confirmei por varredura automática coisas como “o arquivo de saída existe”, “a estrutura do post foi renderizada”, “os links de idioma não estão quebrados” e “os dados do calendário existem”.
Esse era o centro deste trabalho.
Otimização de build não é só reduzir tempo. É explicitar de novo o contrato das funcionalidades existentes.
O que aprendi desta vez
Jekyll parece simples porque é um static site generator.
Mas, se dentro do Liquid você começa a varrer o site inteiro continuamente, até um site estático fica pesado o bastante.
Em uma estrutura multilíngue, pequenas ineficiências crescem imediatamente por multiplicação.
número de páginas * número de idiomas * número de menus * número total de posts
Quando esse tipo de multiplicação está escondido, o build explode de repente depois.
O que aprendi desta vez é simples.
Primeiro, não repetir inline os mesmos dados em todas as páginas.
Segundo, se a entrada é igual, cachear o include.
Terceiro, não varrer o site inteiro em todas as páginas só para verificar se uma URL existe.
Quarto, não tratar exceções no feeling; colocar em dados.
Quinto, depois da otimização, confirmar o contrato das funcionalidades com verificações automáticas.
Este blog está aos poucos deixando de ser apenas um blog pessoal e virando um sistema.
Isso é bom e cansativo ao mesmo tempo.
Mas, pelo menos desta vez, a parte cansativa teve sentido.
Trazer um build que levava mais de 21 minutos para a faixa de 1 minuto foi, na prática, um ponto de virada bem grande.
Agora existe um mínimo de fôlego para continuar fazendo o blog multilíngue crescer.
Deixe um comentário