Pular para o conteúdo

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

src/layouts/BaseLayout.astro
<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ário
import 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:

src/utils/index.js
// Evite exportar tudo de uma vez
// Em vez disso, permita importações específicas
// ❌ Evite isso
export * 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:

src/components/ComplexChart.js
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

astro.config.mjs
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

src/layouts/BaseLayout.astro
<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 simples
import { format } from 'date-fns';
const formattedDate = format(new Date(), 'dd/MM/yyyy');
// ✅ Use JavaScript nativo
const 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:

astro.config.mjs
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:

tailwind.config.js
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:

astro.config.mjs
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:

  1. Largest Contentful Paint (LCP): < 2.5s
  2. First Input Delay (FID): < 100ms
  3. Cumulative Layout Shift (CLS): < 0.1

Ferramentas de Medição

Lighthouse

Integre testes de Lighthouse no seu CI/CD:

lighthouse.js
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:

src/scripts/web-vitals.js
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 Vitals
onCLS(sendToAnalytics);
onFID(sendToAnalytics);
onLCP(sendToAnalytics);
src/layouts/BaseLayout.astro
<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:

src/pages/dashboard.astro
---
// Partes estáticas são pré-renderizadas, mas o conteúdo do dashboard é dinâmico
export const prerender = false;
// Componentes e dados
import 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:

src/pages/streaming-example.astro
---
// Habilita streaming SSR
export const prerender = false;
// Dados que podem ser carregados imediatamente
const quickData = await getQuickData();
// Função para carregar dados lentos
async 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.js
const express = require('express');
const spdy = require('spdy'); // Para HTTP/2
const fs = require('fs');
const path = require('path');
const app = express();
// Middleware para Server Push
app.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áticos
app.use(express.static('dist'));
// Configuração do servidor HTTP/2
const options = {
key: fs.readFileSync('./ssl/server.key'),
cert: fs.readFileSync('./ssl/server.crt')
};
// Iniciar servidor
spdy.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:


Última atualização: 21 de março de 2025