13. SEO e Performance
Este documento apresenta estratégias e práticas para otimizar soluções web desenvolvidas com Astro 5, focando em SEO (Search Engine Optimization) e performance.
Otimização para Mecanismos de Busca (SEO)
Metadados Essenciais
Implemente metadados essenciais para melhorar a visibilidade nos mecanismos de busca:
---const { title, description, canonicalUrl, image} = Astro.props;
const siteUrl = 'https://exemplo.com';const defaultImage = '/images/og-default.jpg';---
<html lang="pt-BR"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Metadados básicos --> <title>{title}</title> <meta name="description" content={description}> <link rel="canonical" href={canonicalUrl || Astro.url.href}>
<!-- Open Graph / Facebook --> <meta property="og:type" content="website"> <meta property="og:url" content={Astro.url.href}> <meta property="og:title" content={title}> <meta property="og:description" content={description}> <meta property="og:image" content={new URL(image || defaultImage, siteUrl).href}>
<!-- Twitter --> <meta property="twitter:card" content="summary_large_image"> <meta property="twitter:url" content={Astro.url.href}> <meta property="twitter:title" content={title}> <meta property="twitter:description" content={description}> <meta property="twitter:image" content={new URL(image || defaultImage, siteUrl).href}>
<!-- Favicon --> <link rel="icon" type="image/png" href="/favicon.png"> <link rel="apple-touch-icon" href="/apple-touch-icon.png">
<slot name="head" /></head><body> <slot /></body></html>Estrutura de URL
Crie URLs amigáveis e descritivas:
# ❌ URLs não amigáveishttps://exemplo.com/p?id=123https://exemplo.com/category.php?id=5
# ✅ URLs amigáveishttps://exemplo.com/produtos/cadeira-ergonomicahttps://exemplo.com/categorias/moveis-escritorioSitemap XML
Crie um sitemap XML para ajudar os mecanismos de busca a indexar seu site:
---import { getCollection } from 'astro:content';
export async function GET({ site }) { const produtos = await getCollection('produtos'); const categorias = await getCollection('categorias');
const produtosEntries = produtos.map((produto) => ({ url: `${site.origin}/produtos/${produto.slug}`, lastmod: produto.data.updatedAt || produto.data.publishedAt, }));
const categoriasEntries = categorias.map((categoria) => ({ url: `${site.origin}/categorias/${categoria.slug}`, lastmod: categoria.data.updatedAt || categoria.data.publishedAt, }));
// Páginas estáticas const staticPages = [ { url: site.origin, lastmod: new Date().toISOString().split('T')[0], }, { url: `${site.origin}/sobre`, lastmod: new Date().toISOString().split('T')[0], }, { url: `${site.origin}/contato`, lastmod: new Date().toISOString().split('T')[0], }, ];
const allPages = [...staticPages, ...produtosEntries, ...categoriasEntries];
return new Response( `<?xml version="1.0" encoding="UTF-8"?> <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> ${allPages.map((page) => ` <url> <loc>${page.url}</loc> <lastmod>${page.lastmod}</lastmod> </url> `).join('')} </urlset>`, { headers: { 'Content-Type': 'application/xml', }, } );}Robots.txt
Crie um arquivo robots.txt para controlar o acesso dos crawlers:
---export function GET({ site }) { return new Response( `User-agent: *Allow: /
Sitemap: ${site.origin}/sitemap.xml`, { headers: { 'Content-Type': 'text/plain', }, } );}Dados Estruturados (Schema.org)
Implemente dados estruturados para melhorar a exibição nos resultados de busca:
---const { product } = Astro.props;const schemaData = { "@context": "https://schema.org", "@type": "Product", "name": product.name, "description": product.description, "image": product.imageUrl, "sku": product.sku, "brand": { "@type": "Brand", "name": product.brand }, "offers": { "@type": "Offer", "price": product.price, "priceCurrency": "BRL", "availability": product.inStock ? "https://schema.org/InStock" : "https://schema.org/OutOfStock" }};---
<script type="application/ld+json" set:html={JSON.stringify(schemaData)} />Uso do componente:
---import BaseLayout from '../layouts/BaseLayout.astro';import ProductSchema from '../components/ProductSchema.astro';
const product = { name: "Cadeira Ergonômica Deluxe", description: "Cadeira ergonômica com ajustes de altura e encosto para máximo conforto.", imageUrl: "https://exemplo.com/images/cadeira-ergonomica.jpg", sku: "ERG-123", brand: "ComfortPlus", price: 999.99, inStock: true};---
<BaseLayout title={product.name} description={product.description} image={product.imageUrl}> <ProductSchema product={product} />
<main> <h1>{product.name}</h1> <p>{product.description}</p> <!-- Resto do conteúdo da página --> </main></BaseLayout>Otimização de Performance
Core Web Vitals
Os Core Web Vitals são métricas essenciais para uma boa experiência do usuário:
-
Largest Contentful Paint (LCP): Mede o tempo de carregamento do maior elemento visível
- Objetivo: < 2.5 segundos
-
First Input Delay (FID): Mede o tempo até que o site responda à primeira interação
- Objetivo: < 100 milissegundos
-
Cumulative Layout Shift (CLS): Mede a estabilidade visual durante o carregamento
- Objetivo: < 0.1
Otimização de Imagens
Componente de Imagem Otimizada
---import { Image } from 'astro:assets';
const { src, alt, width, height, loading = 'lazy', format = 'webp', quality = 80, class: className, ...props} = Astro.props;---
<Image src={src} alt={alt} width={width} height={height} loading={loading} format={format} quality={quality} class={className} {...props}/>Uso do componente:
---import OptimizedImage from '../components/OptimizedImage.astro';import heroImage from '../assets/images/hero.jpg';---
<!-- Imagem hero carrega com prioridade --><OptimizedImage src={heroImage} alt="Banner principal do site" width={1200} height={600} loading="eager" class="hero-image"/>
<!-- Imagens abaixo da dobra carregam com lazy loading --><div class="gallery"> {gallery.map(image => ( <OptimizedImage src={image.src} alt={image.alt} width={400} height={300} class="gallery-image" /> ))}</div>Imagens Responsivas
---import { getImage } from 'astro:assets';import heroImage from '../assets/images/hero.jpg';
// Gerar variantes da imagemconst smallImage = await getImage({ src: heroImage, width: 480, format: 'webp',});
const mediumImage = await getImage({ src: heroImage, width: 768, format: 'webp',});
const largeImage = await getImage({ src: heroImage, width: 1200, format: 'webp',});---
<img src={largeImage.src} alt="Banner principal do site" srcset={` ${smallImage.src} 480w, ${mediumImage.src} 768w, ${largeImage.src} 1200w `} sizes="(max-width: 480px) 480px, (max-width: 768px) 768px, 1200px" width={1200} height={600} loading="eager" class="hero-image"/>Otimização de Fontes
Carregue fontes de forma eficiente:
<html lang="pt-BR"><head> <!-- ... outros metadados ... -->
<!-- Preload de fontes críticas --> <link rel="preload" href="/fonts/inter-var.woff2" as="font" type="font/woff2" crossorigin />
<style is:inline> /* Font-face inline para fontes críticas */ @font-face { font-family: 'Inter'; font-style: normal; font-weight: 100 900; font-display: swap; src: url('/fonts/inter-var.woff2') format('woff2'); }
/* Aplicar fonte ao body */ body { font-family: 'Inter', sans-serif; } </style>
<!-- Fontes não críticas carregadas normalmente --> <link rel="stylesheet" href="/styles/fonts.css" /></head><body> <slot /></body></html>Conteúdo de /styles/fonts.css:
/* Fontes adicionais não críticas */@font-face { font-family: 'Playfair Display'; font-style: normal; font-weight: 400 700; font-display: swap; src: url('/fonts/playfair-display-var.woff2') format('woff2');}
/* Classes para usar a fonte */.heading { font-family: 'Playfair Display', serif;}Otimização de CSS
CSS Crítico Inline
---import { getCriticalCSS } from '../utils/css';
const criticalCSS = await getCriticalCSS();---
<html lang="pt-BR"><head> <!-- ... outros metadados ... -->
<!-- CSS crítico inline --> <style is:inline set:html={criticalCSS}></style>
<!-- CSS não crítico carregado de forma assíncrona --> <link rel="preload" href="/styles/main.css" as="style" onload="this.onload=null;this.rel='stylesheet'" /> <noscript> <link rel="stylesheet" href="/styles/main.css"> </noscript></head><body> <slot /></body></html>Implementação de getCriticalCSS:
import fs from 'fs/promises';import path from 'path';import { fileURLToPath } from 'url';
export async function getCriticalCSS() { try { const __dirname = path.dirname(fileURLToPath(import.meta.url)); const criticalCSSPath = path.join(__dirname, '../../public/styles/critical.css');
return await fs.readFile(criticalCSSPath, 'utf-8'); } catch (error) { console.error('Erro ao carregar CSS crítico:', error); return ''; }}Otimização de JavaScript
Carregamento de Scripts
<html lang="pt-BR"><head> <!-- ... outros metadados ... --></head><body> <slot />
<!-- Scripts essenciais carregados com maior prioridade --> <script src="/scripts/essential.js"></script>
<!-- Scripts não essenciais carregados com defer --> <script src="/scripts/analytics.js" defer></script> <script src="/scripts/features.js" defer></script>
<!-- Scripts que precisam ser executados após o carregamento completo --> <script> window.addEventListener('load', () => { // Carregar scripts adicionais dinamicamente const lazyScript = document.createElement('script'); lazyScript.src = '/scripts/lazy-loaded.js'; document.body.appendChild(lazyScript); }); </script></body></html>Módulos JavaScript Otimizados
<div class="interactive-feature" data-feature-id="gallery"> <!-- Conteúdo do componente --></div>
<script> // Este script será hidratado automaticamente // e processado pelo bundler do Astro const featureElement = document.querySelector('.interactive-feature');
if (featureElement) { // Carregar o módulo apenas quando necessário const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { import('/scripts/features/gallery.js') .then(module => { module.initGallery(featureElement); }) .catch(error => { console.error('Erro ao carregar o módulo:', error); });
// Desconectar o observer após carregar observer.disconnect(); } }); });
observer.observe(featureElement); }</script>Otimização de Recursos de Terceiros
Componente para Analytics
---const { id } = Astro.props;---
<!-- Google Analytics / Google Tag Manager --><script define:vars={{ id }} type="text/partytown"> // Código de analytics window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag('js', new Date()); gtag('config', id);</script>Configuração do Partytown no astro.config.mjs:
import { defineConfig } from 'astro/config';import partytown from '@astrojs/partytown';
export default defineConfig({ integrations: [ partytown({ // Configurações do Partytown config: { forward: ['dataLayer.push'], }, }), ],});Técnicas Avançadas de Performance
Pré-carregamento de Recursos Críticos
---const criticalResources = [ { href: '/fonts/inter-var.woff2', as: 'font', type: 'font/woff2', crossorigin: true }, { href: '/styles/main.css', as: 'style' }, { href: '/scripts/essential.js', as: 'script' }, // Preconectar a domínios de terceiros { href: 'https://analytics.example.com', rel: 'preconnect' },];---
<html lang="pt-BR"><head> <!-- ... outros metadados ... -->
{criticalResources.map(resource => ( resource.rel === 'preconnect' ? <link rel="preconnect" href={resource.href} crossorigin /> : <link rel="preload" href={resource.href} as={resource.as} type={resource.type} crossorigin={resource.crossorigin ? true : undefined} /> ))}</head><body> <slot /></body></html>Componente de Imagem com Lazy Loading e Blur-up
---import { getImage } from 'astro:assets';
const { src, alt, width, height, class: className } = Astro.props;
// Gerar versão em baixa resolução para placeholderconst placeholderImage = await getImage({ src, width: 20, height: Math.round(20 * (height / width)), format: 'webp', quality: 30,});
// Gerar imagem otimizadaconst optimizedImage = await getImage({ src, width, height, format: 'webp', quality: 80,});
// ID único para este componenteconst id = `blur-image-${Math.random().toString(36).substring(2, 11)}`;---
<div class={`blur-image-container ${className || ''}`} style={`aspect-ratio: ${width}/${height};`}> <!-- Imagem placeholder (blur) --> <img src={placeholderImage.src} alt="" width={placeholderImage.options.width} height={placeholderImage.options.height} class="blur-image-placeholder" aria-hidden="true" id={`${id}-placeholder`} />
<!-- Imagem real (carregada com lazy loading) --> <img src={optimizedImage.src} alt={alt} width={width} height={height} loading="lazy" class="blur-image-full" id={`${id}-full`} onload={`document.getElementById('${id}-placeholder').style.opacity = 0;`} /></div>
<style> .blur-image-container { position: relative; overflow: hidden; background-color: #f0f0f0; }
.blur-image-placeholder { position: absolute; top: 0; left: 0; width: 100%; height: 100%; filter: blur(10px); transform: scale(1.1); opacity: 1; transition: opacity 0.3s ease-in-out; object-fit: cover; }
.blur-image-full { position: relative; width: 100%; height: 100%; object-fit: cover; }</style>Estratégia de Cache
Configuração de cabeçalhos de cache no Netlify (netlify.toml):
[[headers]] for = "/*" [headers.values] Cache-Control = "public, max-age=0, must-revalidate"
[[headers]] for = "/_astro/*" [headers.values] Cache-Control = "public, max-age=31536000, immutable"
[[headers]] for = "/fonts/*" [headers.values] Cache-Control = "public, max-age=31536000, immutable"
[[headers]] for = "/images/*" [headers.values] Cache-Control = "public, max-age=31536000, immutable"Ferramentas de Análise e Monitoramento
Lighthouse
Lighthouse é uma ferramenta automatizada de código aberto para melhorar a qualidade de páginas web:
# Instalar Lighthouse CLInpm install -g lighthouse
# Executar análiselighthouse https://exemplo.com --viewWeb Vitals
Monitore os Web Vitals em produção:
import { onCLS, onFID, onLCP } from 'web-vitals';
function sendToAnalytics({ name, delta, id }) { // Código para enviar métricas para sua ferramenta de analytics console.log(`Metric: ${name} | Value: ${delta} | ID: ${id}`);
// Exemplo com Google Analytics if (window.gtag) { gtag('event', name, { value: delta, metric_id: id, metric_value: delta, metric_delta: delta, }); }}
// Monitorar Core Web VitalsonCLS(sendToAnalytics);onFID(sendToAnalytics);onLCP(sendToAnalytics);Inclusão do script:
<html lang="pt-BR"><head> <!-- ... outros metadados ... --></head><body> <slot />
<!-- Monitoramento de Web Vitals --> <script type="module"> import '/scripts/web-vitals.js'; </script></body></html>Lista de Verificação de SEO e Performance
Use esta lista para verificar a otimização do seu site:
SEO
- Metadados completos (title, description, canonical)
- Open Graph e Twitter Cards implementados
- Dados estruturados (Schema.org) para conteúdo relevante
- URLs amigáveis e descritivas
- Sitemap XML atualizado
- Robots.txt configurado corretamente
- Conteúdo otimizado para palavras-chave relevantes
- Estrutura de cabeçalhos (h1-h6) adequada
- Texto alternativo em imagens
- Links internos bem estruturados
Performance
- Imagens otimizadas e responsivas
- Fontes web otimizadas
- CSS crítico inline
- JavaScript carregado de forma eficiente
- Recursos de terceiros isolados ou otimizados
- Pré-carregamento de recursos críticos
- Estratégia de cache implementada
- Compressão de recursos habilitada
- Core Web Vitals dentro dos limites recomendados
- Monitoramento de performance em produção
Conclusão
A otimização de SEO e performance é essencial para o sucesso de qualquer solução web. Ao seguir as práticas recomendadas neste documento, você estará criando sites que não apenas são facilmente descobertos pelos mecanismos de busca, mas também oferecem uma experiência rápida e agradável para os usuários.
Lembre-se de que a otimização é um processo contínuo. Monitore regularmente as métricas de SEO e performance do seu site e faça ajustes conforme necessário para manter e melhorar os resultados.
Os próximos documentos detalharão outros aspectos do desenvolvimento:
- Implantação e DevOps: Estratégias de implantação e operações
- Internacionalização: Suporte a múltiplos idiomas e regiões
Última atualização: 21 de março de 2025