7. Otimizações de Performance
Este documento detalha estratégias e técnicas para otimizar a performance de soluções web desenvolvidas com Astro 5, garantindo sites rápidos, eficientes e com boa experiência do usuário.
Estratégias de Carregamento
Carregamento Prioritário
Priorize o carregamento de recursos críticos para melhorar métricas como Largest Contentful Paint (LCP) e First Input Delay (FID):
Priorização de CSS Crítico
<html lang="pt-BR"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width" /> <title>{Astro.props.title || 'Site Default'}</title>
<!-- CSS crítico inline para renderização inicial --> <style is:inline> /* Estilos críticos para o primeiro render */ body { font-family: system-ui, sans-serif; margin: 0; padding: 0; } .header { padding: 1rem; background-color: #f9fafb; border-bottom: 1px solid #e5e7eb; } .main-content { max-width: 1200px; margin: 0 auto; padding: 1rem; } /* Mais estilos críticos... */ </style>
<!-- CSS não-crítico carregado de forma assíncrona --> <link rel="stylesheet" href="/styles/main.css" media="print" onload="this.media='all'" /> </head> <body> <slot /> </body></html>Preload de Recursos Críticos
<head> <!-- Preload da fonte principal --> <link rel="preload" href="/fonts/Inter-var.woff2" as="font" type="font/woff2" crossorigin />
<!-- Preload da imagem hero --> <link rel="preload" href="/images/hero.webp" as="image" type="image/webp" />
<!-- Preload de scripts críticos --> <link rel="modulepreload" href="/scripts/critical.js" /></head>Carregamento Lazy
Adie o carregamento de recursos não críticos para melhorar o tempo de carregamento inicial:
Imagens com Lazy Loading
---import { Image } from 'astro:assets';import type { ImageMetadata } from 'astro';
interface Props { src: ImageMetadata | string; alt: string; width?: number; height?: number; loading?: 'eager' | 'lazy'; class?: string;}
const { src, alt, width, height, loading = 'lazy', class: className = '',} = Astro.props;---
<Image src={src} alt={alt} width={width} height={height} loading={loading} class={className}/>Componentes com Lazy Loading
---// Carrega componentes pesados apenas quando necessárioimport HeavyComponent from '../components/HeavyComponent.jsx';---
<!-- Componente será hidratado apenas quando visível --><HeavyComponent client:visible />Bundle Splitting
O Astro realiza automaticamente o bundle splitting, mas você pode otimizá-lo ainda mais:
Módulos JavaScript
Organize seu código em módulos pequenos e focados:
// Evite exportar tudo de uma vez// Em vez disso, permita importações específicas
// ❌ Evite issoexport * from './date.js';export * from './string.js';export * from './validation.js';export * from './animation.js';
// ✅ Prefira importações específicas nos componentes// import { formatDate } from '../utils/date.js';Carregamento Dinâmico
Use importações dinâmicas para código que não é necessário imediatamente:
document.querySelector('#load-chart').addEventListener('click', async () => { // Carrega a biblioteca de gráficos apenas quando necessário const { Chart } = await import('chart.js/auto');
// Inicializa o gráfico const ctx = document.querySelector('#chart').getContext('2d'); new Chart(ctx, { type: 'bar', data: { // Dados do gráfico... }, options: { // Opções do gráfico... } });});Lazy Loading
Imagens e Iframes
Utilize atributos de lazy loading nativos:
<!-- Lazy loading nativo para imagens --><img src="/images/large-image.jpg" alt="Descrição da imagem" loading="lazy" width="800" height="600"/>
<!-- Lazy loading para iframes --><iframe src="https://www.youtube.com/embed/VIDEO_ID" loading="lazy" title="Vídeo do YouTube"></iframe>Componentes Interativos
Utilize as diretivas de cliente do Astro para lazy loading de componentes interativos:
---import Comments from '../components/Comments.jsx';import RelatedPosts from '../components/RelatedPosts.astro';import VideoPlayer from '../components/VideoPlayer.jsx';---
<!-- Carrega apenas quando o usuário rola até esta seção --><section class="mt-12"> <h2 class="text-2xl font-bold mb-6">Vídeo Relacionado</h2> <VideoPlayer client:visible videoId="VIDEO_ID" /></section>
<!-- Carrega quando o navegador fica ocioso --><RelatedPosts />
<!-- Carrega apenas quando visível --><section class="mt-12"> <h2 class="text-2xl font-bold mb-6">Comentários</h2> <Comments client:visible /></section>Otimização de Imagens
O Astro 5 inclui otimização de imagens integrada:
Componente Image
---import { Image } from 'astro:assets';import heroImage from '../assets/images/hero.jpg';---
<!-- Imagem otimizada com dimensões específicas --><Image src={heroImage} alt="Imagem de destaque" width={1200} height={600} format="webp" quality={80}/>
<!-- Imagem responsiva --><Image src={heroImage} alt="Imagem de destaque" widths={[400, 800, 1200]} sizes="(max-width: 640px) 400px, (max-width: 1024px) 800px, 1200px" format="webp"/>Configuração de Otimização de Imagens
import { defineConfig } from 'astro/config';import image from '@astrojs/image';
export default defineConfig({ integrations: [ image({ serviceEntryPoint: '@astrojs/image/sharp', cacheDir: './.cache/image', logLevel: 'info', }), ], image: { domains: ['images.unsplash.com'], // Domínios permitidos para imagens remotas remotePatterns: [{ protocol: 'https' }], format: ['webp', 'avif', 'png', 'jpeg'], fallback: 'png', quality: 80, },});Otimização de Fontes
Estratégia de Carregamento de Fontes
<html lang="pt-BR"> <head> <!-- ... outros meta tags ... -->
<!-- Preconnect para origens de fontes --> <link rel="preconnect" href="https://fonts.googleapis.com" /> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<!-- Font Display Swap --> <style is:inline> @font-face { font-family: 'Inter'; font-style: normal; font-weight: 400 700; font-display: swap; src: url('/fonts/inter-var.woff2') format('woff2-variations'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } </style>
<!-- Fallback de fonte --> <style is:inline> body { font-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; } </style> </head> <body> <slot /> </body></html>Subconjuntos de Fontes
<!-- Carrega apenas os caracteres necessários --><link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap&text=ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" rel="stylesheet"/>Otimização de JavaScript
Redução de Dependências
Prefira JavaScript vanilla sempre que possível:
// ❌ Evite importar bibliotecas inteiras para funcionalidades simplesimport { format } from 'date-fns';const formattedDate = format(new Date(), 'dd/MM/yyyy');
// ✅ Use JavaScript nativoconst formattedDate = new Date().toLocaleDateString('pt-BR');Minificação e Tree Shaking
O Astro realiza minificação e tree shaking automaticamente durante o build. Configure opções adicionais:
import { defineConfig } from 'astro/config';
export default defineConfig({ build: { // Opções de build format: 'file', assets: 'assets', inlineStylesheets: 'auto', // 'always', 'never', ou 'auto' }, vite: { build: { // Configurações do Vite para otimização minify: 'terser', terserOptions: { compress: { drop_console: true, // Remove console.log em produção }, }, rollupOptions: { output: { manualChunks: { // Agrupa dependências comuns vendor: ['lodash', 'axios'], }, }, }, }, },});Otimização de CSS
Purge CSS
O Tailwind CSS já inclui PurgeCSS para remover classes não utilizadas:
module.exports = { content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'], // Outras configurações...};CSS Crítico
Extraia CSS crítico automaticamente com o plugin Astro Critters:
import { defineConfig } from 'astro/config';import critters from 'astro-critters';
export default defineConfig({ integrations: [ critters({ // Opções do Critters preload: 'swap', pruneSource: true, }), ],});Métricas de Performance e Benchmarks
Core Web Vitals
Monitore as Core Web Vitals para garantir boa performance:
- Largest Contentful Paint (LCP): < 2.5s
- First Input Delay (FID): < 100ms
- Cumulative Layout Shift (CLS): < 0.1
Ferramentas de Medição
Lighthouse
Integre testes de Lighthouse no seu CI/CD:
const lighthouse = require('lighthouse');const chromeLauncher = require('chrome-launcher');
async function runLighthouse(url) { const chrome = await chromeLauncher.launch({chromeFlags: ['--headless']}); const options = { logLevel: 'info', output: 'html', port: chrome.port, onlyCategories: ['performance', 'accessibility', 'best-practices', 'seo'], };
const runnerResult = await lighthouse(url, options); await chrome.kill();
// Verifica se as pontuações atendem aos requisitos const scores = Object.entries(runnerResult.lhr.categories).map(([key, value]) => { return { category: key, score: value.score * 100 }; });
console.table(scores);
// Falha se a performance estiver abaixo de 90 if (runnerResult.lhr.categories.performance.score < 0.9) { console.error('Performance score is below threshold!'); process.exit(1); }
return runnerResult;}
runLighthouse('http://localhost:3000');Web Vitals no Cliente
Monitore as métricas no mundo real:
import { onCLS, onFID, onLCP } from 'web-vitals';
function sendToAnalytics({ name, delta, id }) { // Código para enviar métricas para seu serviço de analytics console.log(`Metric: ${name} | Value: ${delta} | ID: ${id}`);
// Exemplo: enviar para Google Analytics window.gtag?.('event', name, { value: Math.round(delta), metric_id: id, metric_value: delta, metric_delta: delta, });}
// Monitora Core Web VitalsonCLS(sendToAnalytics);onFID(sendToAnalytics);onLCP(sendToAnalytics);<html lang="pt-BR"> <head> <!-- ... outros meta tags ... --> </head> <body> <slot />
<!-- Script de monitoramento de Web Vitals --> <script src="../scripts/web-vitals.js" type="module"></script> </body></html>Otimizações Avançadas
Pré-renderização Parcial
Para páginas com conteúdo dinâmico e estático:
---// Partes estáticas são pré-renderizadas, mas o conteúdo do dashboard é dinâmicoexport const prerender = false;
// Componentes e dadosimport DashboardLayout from '../layouts/DashboardLayout.astro';import UserInfo from '../components/UserInfo.jsx';import DashboardStats from '../components/DashboardStats.jsx';---
<DashboardLayout title="Dashboard"> <!-- Conteúdo estático pré-renderizado --> <header class="mb-8"> <h1 class="text-3xl font-bold">Dashboard</h1> <p class="text-gray-600">Bem-vindo ao seu painel de controle</p> </header>
<!-- Componente dinâmico que será hidratado no cliente --> <UserInfo client:load />
<!-- Estatísticas que serão carregadas quando visíveis --> <DashboardStats client:visible /></DashboardLayout>Streaming SSR
Para melhorar o Time to First Byte (TTFB) em páginas complexas:
---// Habilita streaming SSRexport const prerender = false;
// Dados que podem ser carregados imediatamenteconst quickData = await getQuickData();
// Função para carregar dados lentosasync function getSlowData() { // Simula uma operação lenta await new Promise(resolve => setTimeout(resolve, 2000)); return { items: ['Item 1', 'Item 2', 'Item 3'] };}---
<html> <head> <title>Streaming SSR Example</title> </head> <body> <h1>Streaming SSR</h1>
<!-- Conteúdo que é renderizado imediatamente --> <div> <h2>Dados Rápidos</h2> <pre>{JSON.stringify(quickData, null, 2)}</pre> </div>
<!-- Conteúdo que será transmitido quando disponível --> <div> <h2>Dados Lentos</h2> {(async () => { const slowData = await getSlowData(); return ( <pre>{JSON.stringify(slowData, null, 2)}</pre> ); })()} </div> </body></html>HTTP/2 Server Push
Configure seu servidor para usar HTTP/2 Server Push para recursos críticos:
// Exemplo com Express e Node.jsconst express = require('express');const spdy = require('spdy'); // Para HTTP/2const fs = require('fs');const path = require('path');
const app = express();
// Middleware para Server Pushapp.use((req, res, next) => { const pushAssets = [ '/styles/main.css', '/scripts/main.js', '/fonts/inter-var.woff2', ];
if (res.push && req.path === '/') { pushAssets.forEach(asset => { const assetPath = path.join(__dirname, 'dist', asset); const stream = res.push(asset, { request: { accept: '*/*' }, response: { 'content-type': asset.endsWith('.css') ? 'text/css' : asset.endsWith('.js') ? 'application/javascript' : 'application/octet-stream' } });
stream.on('error', err => { console.error(`Error pushing ${asset}:`, err); });
fs.createReadStream(assetPath).pipe(stream); }); }
next();});
// Servir arquivos estáticosapp.use(express.static('dist'));
// Configuração do servidor HTTP/2const options = { key: fs.readFileSync('./ssl/server.key'), cert: fs.readFileSync('./ssl/server.crt')};
// Iniciar servidorspdy.createServer(options, app).listen(3000, () => { console.log('Server running on https://localhost:3000');});Conclusão
A otimização de performance é um processo contínuo que deve ser considerado em todas as fases do desenvolvimento. Ao implementar as estratégias descritas neste documento, você pode criar soluções web rápidas, eficientes e com boa experiência do usuário.
Lembre-se de medir regularmente a performance do seu site e fazer ajustes conforme necessário. Um site rápido não apenas melhora a experiência do usuário, mas também contribui para melhor SEO e maiores taxas de conversão.
Os próximos documentos detalharão outros aspectos do desenvolvimento:
- Integração com Backends: Configuração de backends e APIs
- Ferramentas de Desenvolvimento: Setup do ambiente de desenvolvimento
- Padrões de JavaScript: Organização de funções e módulos
Última atualização: 21 de março de 2025