Pular para o conteúdo

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:

src/layouts/BaseLayout.astro
---
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áveis
https://exemplo.com/p?id=123
https://exemplo.com/category.php?id=5
# ✅ URLs amigáveis
https://exemplo.com/produtos/cadeira-ergonomica
https://exemplo.com/categorias/moveis-escritorio

Sitemap XML

Crie um sitemap XML para ajudar os mecanismos de busca a indexar seu site:

src/pages/sitemap.xml.js
---
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:

src/pages/robots.txt.js
---
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:

src/components/ProductSchema.astro
---
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:

  1. Largest Contentful Paint (LCP): Mede o tempo de carregamento do maior elemento visível

    • Objetivo: < 2.5 segundos
  2. First Input Delay (FID): Mede o tempo até que o site responda à primeira interação

    • Objetivo: < 100 milissegundos
  3. Cumulative Layout Shift (CLS): Mede a estabilidade visual durante o carregamento

    • Objetivo: < 0.1

Otimização de Imagens

Componente de Imagem Otimizada

src/components/OptimizedImage.astro
---
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 imagem
const 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:

src/layouts/BaseLayout.astro
<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

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

src/utils/css.js
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

src/layouts/BaseLayout.astro
<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

src/components/InteractiveFeature.astro
<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

src/components/Analytics.astro
---
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

src/layouts/BaseLayout.astro
---
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

src/components/BlurImage.astro
---
import { getImage } from 'astro:assets';
const { src, alt, width, height, class: className } = Astro.props;
// Gerar versão em baixa resolução para placeholder
const placeholderImage = await getImage({
src,
width: 20,
height: Math.round(20 * (height / width)),
format: 'webp',
quality: 30,
});
// Gerar imagem otimizada
const optimizedImage = await getImage({
src,
width,
height,
format: 'webp',
quality: 80,
});
// ID único para este componente
const 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:

Terminal window
# Instalar Lighthouse CLI
npm install -g lighthouse
# Executar análise
lighthouse https://exemplo.com --view

Web Vitals

Monitore os Web Vitals em produção:

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

Inclusão do script:

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


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