Preparando o ambiente para criar sites com Astro
Antes de criar o nosso primeiro site, precisamos deixar o computador pronto. São só 3 passos simples, sem mistério.
- 01
Baixe e instale o Node.js
O Node.js é a base que permite rodar as ferramentas modernas de desenvolvimento web. Acesse o site oficial, baixe a versão recomendada (LTS) para o seu sistema e siga o instalador.
Baixar Node.js - 02
Baixe e instale o Antigravity IDE
Vamos usar o Antigravity IDE como editor de código ao longo do curso, mas você pode usar qualquer outra IDE de sua preferência (como o VS Code, por exemplo).
Baixar Antigravity IDE - 03
Crie e rode o seu primeiro projeto Astro
Com o Node.js e a IDE instalados, abra o terminal na pasta onde quer criar o projeto e rode o comando abaixo para criar um novo projeto Astro.
Criar o projetonpm create astro@latest
Depois de criar, entre na pasta do projeto
ouPelo terminal
Use o comando abaixo (troque "nome-do-projeto" pelo nome que você escolheu na criação).
cd nome-do-projeto
Pelos arquivos
Abra o explorador de arquivos, encontre a pasta criada e abra ela direto no Antigravity.
Rodar o projeto localmente (sempre que quiser)npm run dev
Reorganizando um site HTML existente no Astro
Se você já tem uma landing page, portfólio ou site antigo em HTML/CSS, não é preciso jogar nada fora. O Astro permite organizar esse material existente em uma arquitetura profissional.
- 01
Reorganização, não reconstrução
Um site feito em HTML puro já tem vários elementos úteis (textos, imagens, seções, estilos, botões). A ideia do Astro não é apagar o que você já sabe ou descartar o que você construiu, mas sim organizar esse material em páginas, layouts e componentes reaproveitáveis.
- 02
Prompt para converter HTML em Astro com IA
Em vez de pedir para a IA refazer o site inteiro de uma vez (o que gera confusão), peça por partes. Anexe seu arquivo HTML de origem e seu arquivo inicial .astro de destino nos locais indicados no prompt abaixo:
Prompt para a IA:[coloque o site HTML aqui]transforme isso em .astro puro, coloque aqui [coloque o arquiv inicial astro aqui] - 03
Organize o projeto por partes
Após converter a página inicial, separe o cabeçalho repetido em um componente (
Header.astro), o rodapé em (Footer.astro) e as marcações base de HTML em (Layout.astro). Esse fluxo facilita a manutenção e prepara o projeto para crescer no próximo módulo!
Duas dobras novas na home: marquee e vídeo
Primeiro uma faixa de palavras deslizando sem parar, em fonte cursiva e ocupando a largura inteira da tela. Logo abaixo, uma dobra de vídeo bem mais alta, sem nenhuma camada por cima. Duas peças simples que dão ritmo à página inicial e reforçam a identidade da caneta.
- 01
Prompt 1 — a dobra do marquee
Copie o prompt abaixo e envie para a IA com o projeto aberto. Ele descreve os quatro pontos que importam: onde a dobra entra, o movimento infinito, o estilo da fonte e a largura total.
PromptCrie para mim uma nova dobra na página inicial, essa dobra deve ter um markee infinito com palavras que remetam a essa caneta, use uma fonte cursiva e o fundo dessa dobra deve ser 100%.
- 02
Prompt 2 — a dobra de vídeo logo abaixo
Com o marquee no lugar, a segunda dobra entra em seguida reaproveitando um vídeo que já existe no projeto. Repare que o prompt define a altura em relação à dobra anterior e deixa explícito o que não deve acontecer — dizer o que evitar é tão importante quanto dizer o que fazer.
PromptCrie outra dobra que deve ir abaixo do markee. Essa nova dobra deve utilizar o mesmo vídeo da última dobra, porém eu quero uma altura 80% maior que a altura da dobra do markee. Não utilize overlay no vídeo.
- 03
Prompt 3 — a tabela de especificações
Este descreve só o visual, sem citar nome de arquivo, classe ou caminho — funciona em qualquer projeto e com qualquer IA. Troque os dados das linhas pelos do seu produto.
PromptCrie uma dobra de especificações técnicas com fundo preto, largura total e bastante respiro vertical. No topo, alinhado à esquerda: um pontinho seguido de um rótulo em fonte monoespaçada, maiúsculo e com bastante espaçamento entre letras; abaixo um título grande; abaixo um parágrafo curto em cinza claro com a largura limitada. Depois, a lista de especificações. Não use <table>: monte cada linha como um bloco flex com o nome da spec à esquerda (mono, maiúsculo, pequeno, em negrito) e o valor à direita (mono, um pouco maior). Separe as linhas com divisórias finas e coloque borda também no topo e no fim da lista. No hover, o fundo da linha escurece de leve e o texto clareia. No celular, o valor desce para baixo do nome. Feche com uma linha de rodapé em mono bem pequeno, com um texto em cada ponta.
- 04
Confira o resultado
No marquee, o laço precisa ser contínuo: a lista de palavras é duplicada para que o fim encoste no começo sem buraco nem solavanco. O fundo precisasangrar até as bordas, sem respeitar o container central do resto da página. E a faixa não pode gerar rolagem horizontal — o movimento acontece dentro de um bloco com
overflow: hidden.Na dobra de vídeo, confirme que nenhuma camada escura ficou sobre a imagem: sem
::before, semdivde overlay, sem gradiente por cima. O vídeo também precisa dos atributos que fazem ele rodar sozinho no celular, e a proporção se mantém comobject-fit: cover.Atributos do vídeo<video autoplay muted loop playsinline> <source src="/videos/caneta.mp4" type="video/mp4" /> </video>
Sobre os 80%: as duas alturas ficam mais fáceis de manter se estiverem amarradas uma na outra em vez de escritas soltas. Se a IA cravar dois valores sem relação, peça para ligar as duas com uma variável.
Altura amarrada à do marquee.marquee { --altura-marquee: 8rem; height: var(--altura-marquee); } .dobra-video { height: calc(var(--altura-marquee) * 1.8); /* 80% maior */ }Na tabela, o detalhe que mais entrega um resultado amador é a divisória: ela precisa ficarsó entre as linhas, sem sobrar uma sobreposta à borda de cima ou de baixo da lista.
- 05
Refine com pedidos curtos
Dificilmente sai perfeito de primeira, e tudo bem. Em vez de reescrever o prompt inteiro, peça um ajuste por vez — assim você vê o efeito de cada mudança.
Ajustes de ritmo e tamanhoDeixe o marquee mais lento. Aumente o tamanho das palavras e o espaçamento entre elas. Troque as palavras do marquee por: precisão, traço, nanquim, papel, desenho.
Acabamento do marqueeAdicione um leve degradê nas laterais para as palavras surgirem e sumirem suavemente. Inverta o sentido do movimento, da esquerda para a direita. Pause a animação quando o mouse passar por cima.
Ajustes da dobra de vídeoNo celular, reduza a altura da dobra de vídeo para não ocupar a tela inteira. Enquadre o vídeo pela parte de cima em vez do centro. Remova qualquer camada, sombra ou gradiente que esteja sobre o vídeo.
Otimizando as imagens do site
Imagem pesada é o que mais derruba a performance de um site. O Astro tem um componente próprio que converte, redimensiona e serve cada foto no formato ideal — sem que você precise editar nada na mão.
- 01
Troque as tags <img> pelo componente <Image />
Mova as fotos para
src/assets/, importe cada uma no frontmatter e substitua as tags<img>nativas pelo<Image />doastro:assets(ou do@astrojs/image, em projetos mais antigos). O Astro cuida da conversão para WebP e do tamanho final do arquivo.Antes — imagem sem otimização:<img src="/assets/caneta.png" alt="Caneta ArtTools" />
Depois — imagem otimizada pelo Astro:--- import { Image } from 'astro:assets'; import caneta from '../assets/caneta.png'; --- <Image src={caneta} alt="Caneta ArtTools" /> - 02
Priorize as imagens da primeira dobra
As imagens que aparecem antes da rolagem (o herói do site, por exemplo) não devem ser carregadas preguiçosamente. Marque-as com
loading="eager"efetchpriority="high"para o navegador buscá-las primeiro. Todas as outras podem continuar com oloading="lazy"padrão.Imagem principal da primeira dobra:<Image src={caneta} alt="Caneta ArtTools" loading="eager" fetchpriority="high" /> - 03
Prepare os arquivos antes de subir para o projeto
O Astro otimiza tudo que está em
src/assets/, mas ele não faz milagre com um original de 8 MB — e vídeo ele não comprime. Passe os arquivos por essas ferramentas antes: as três rodam direto no navegador, de graça e sem instalar nada.Comprimir imagem — Squoosh
Você arrasta a foto e compara o antes e o depois lado a lado, com um controle de qualidade ao vivo. Dá para achar o ponto em que o arquivo despenca de tamanho sem perda visível.
Comprimir vídeo — ffmpeg web
O ffmpeg, o programa padrão do mercado para vídeo, rodando dentro do navegador. Use para reduzir o peso dos vídeos de fundo, que o Astro não otimiza sozinho.
Converter formatos — Convertio
Converte entre praticamente qualquer extensão de imagem e vídeo. Serve para aquele arquivo em formato estranho que o projeto não aceita.
View Transitions: navegação suave entre páginas
Por padrão, cada clique em um link recarrega a página inteira e o site "pisca". Com o Client Router do Astro você ganha a suavidade de um aplicativo (SPA) sem abrir mão da simplicidade das páginas estáticas.
- 01
Ative o Astro Client Router
Basta adicionar o componente
<ClientRouter />dentro do<head>do seu Layout principal. A partir daí, a navegação acontece sem recarregar a página, com transição instantânea entre as rotas.src/layouts/Layout.astro--- import { ClientRouter } from 'astro:transitions'; --- <html lang="pt-BR"> <head> <meta charset="UTF-8" /> <ClientRouter /> </head> <body> <slot /> </body> </html> - 02
Use transition:persist no que não pode piscar
Elementos que se repetem em todas as páginas — como o
Headere oNavbar— não precisam ser recriados a cada navegação. Marque-os comtransition:persiste o topo do site fica parado enquanto só o conteúdo central transiciona.Mantendo o topo do site fixo entre páginas:<Header transition:persist /> <Navbar transition:persist /> <main> <slot /> </main>
- 03
Animação de título com React Bits
Para dar mais vida à primeira dobra, use a animação de título mascarado do React Bits. Copie o componente da página abaixo e adapte o texto para o seu projeto.
Abrir Masked Heading no React Bits
SEO na prática: título, descrição e canonical
O Google lê o <head> do seu site antes de qualquer outra coisa. Nesta aula o Layout passa a receber as informações de SEO por props, com textos padrão da marca caso a página não envie nada.
- 01
Receba as props de SEO no Layout
No frontmatter do
Layout.astro, declaretitle,descriptionecanonicalURLcomo props opcionais. Os valores padrão são a rede de segurança: se uma página esquecer de enviar os dados, o site ainda sai com o texto da marca no lugar de vir vazio.Layout.astro — frontmatter:--- interface Props { title?: string; description?: string; canonicalURL?: string; } const { title = 'ArtTools — Canetas técnicas para quem desenha a sério', description = 'Precisão, acabamento premium e traço constante.', canonicalURL = new URL(Astro.url.pathname, Astro.site).href, } = Astro.props; --- - 02
Monte as tags dentro do <head>
Agora é só usar as variáveis. O
<link rel="canonical">é o que evita que o Google trate a mesma página acessada por endereços diferentes como conteúdo duplicado.Layout.astro — dentro do <head>:<title>{title}</title> <meta name="description" content={description} /> <link rel="canonical" href={canonicalURL} /> <link rel="icon" href="/favicon.svg" /> - 03
Passe os dados em cada página
Cada página tem um assunto diferente — e por isso precisa do seu próprio título e da sua própria descrição. Repetir o mesmo texto nas três é um dos erros de SEO mais comuns. A home fala da caneta e da proposta de valor, a segunda apresenta a coleção completa e a terceira foca no modelo específico e no formulário de aviso de estoque.
index.astro:<Layout title="ArtTools — A caneta que não falha no traço" description="Traço uniforme, corpo em alumínio e pontas de 0.3 a 0.8 mm." >
pagina2.astro:<Layout title="Coleção completa ArtTools — Todos os modelos" description="Todas as espessuras de ponta, acabamentos e edições ArtTools." >
pagina3.astro:<Layout title="ArtTools 0.3 mm — Edição Limitada | Avise-me!" description="Esgotada no momento. Cadastre seu e-mail e saiba na hora da reposição." >
Open Graph e Twitter Cards: o card do WhatsApp
É o Open Graph que decide qual imagem e qual texto aparecem quando alguém compartilha o seu link no WhatsApp, Instagram ou LinkedIn. Sem ele, o link vira uma caixa cinza sem graça.
- 01
Adicione a prop image e as meta tags no Layout
O
Layout.astropassa a aceitar uma prop opcionalimage; se ela não for enviada, usa a imagem padrão da marca. Inclua as tags do Open Graph (og:title,og:description,og:image,og:url,og:type) e do Twitter Card. Atenção a um detalhe que quebra o card na prática: o caminho da imagem precisa virar uma URL absoluta, usandoAstro.siteouAstro.url.src/layouts/Layout.astro--- interface Props { title?: string; description?: string; canonicalURL?: string; image?: string; } const { title = 'ArtTools — Canetas técnicas para quem desenha a sério', description = 'Canetas ArtTools: precisão, acabamento premium e traço constante para ilustradores e designers.', canonicalURL = new URL(Astro.url.pathname, Astro.site).href, image = '/images/og-default.jpg', } = Astro.props; // Redes sociais só aceitam URL absoluta na imagem const imageURL = new URL(image, Astro.site ?? Astro.url).href; --- <head> <title>{title}</title> <meta name="description" content={description} /> <link rel="canonical" href={canonicalURL} /> <!-- Open Graph --> <meta property="og:title" content={title} /> <meta property="og:description" content={description} /> <meta property="og:image" content={imageURL} /> <meta property="og:url" content={canonicalURL} /> <meta property="og:type" content="website" /> <!-- Twitter Card --> <meta name="twitter:card" content="summary_large_image" /> <meta name="twitter:title" content={title} /> <meta name="twitter:description" content={description} /> <meta name="twitter:image" content={imageURL} /> </head> - 02
Passe uma imagem própria em cada página
Cada página merece um card diferente, coerente com o que ela mostra. Salve as imagens em
public/images/e passe o caminho pela propimage.index.astro — a caneta em destaque na bancada de desenho<Layout title="ArtTools — A caneta técnica que não falha no traço" description="Conheça a caneta ArtTools: traço uniforme, corpo em alumínio e pontas de 0.3 a 0.8 mm." image="/images/og-hero.jpg" >
modelos.astro — estojo com todas as pontas e cores<Layout title="Coleção completa ArtTools — Todos os modelos e pontas" description="Veja todas as espessuras de ponta, acabamentos e edições da linha ArtTools." image="/images/og-catalogo.jpg" >
produto.astro — detalhe da ponta 0.3 mm com o selo Edição Limitada<Layout title="Caneta ArtTools 0.3 mm — Edição Limitada | Avise-me quando chegar" description="A ArtTools 0.3 mm está esgotada. Cadastre seu e-mail e seja o primeiro a saber da reposição." image="/images/og-produto-03mm.jpg" >
Hierarquia de títulos e URLs amigáveis
Duas arrumações simples que o Google valoriza muito: um índice de títulos bem organizado e endereços que qualquer pessoa entende só de bater o olho.
- 01
Revise a hierarquia de H1, H2 e H3
Pense nos títulos como o sumário de um livro. Apenas um
<h1>por página, com o assunto principal;<h2>nas seções;<h3>nos subitens e cards de dentro de cada seção. Não pule níveis (nada de<h1>direto para<h3>) e não mexa no visual do site — mantenha as classes CSS que já existem.Estrutura correta de uma página<h1>Caneta ArtTools 0.3 mm</h1> <!-- só um por página --> <h2>Características técnicas</h2> <!-- seção --> <h3>Ponta e espessura do traço</h3> <!-- card dentro da seção --> <h3>Material do corpo</h3> <h2>Modelos disponíveis</h2> <h3>Edição Limitada</h3> - 02
Renomeie as rotas para slugs legíveis
No Astro, o nome do arquivo é a URL. Renomeie os arquivos dentro de
src/pages/para nomes que descrevem a página, e transforme a página de detalhes em uma rota dinâmica.Antes → depoissrc/pages/index.astro → / (mantém) src/pages/pagina2.astro → /catalogo (src/pages/catalogo.astro) src/pages/pagina3.astro → /produtos/:slug (src/pages/produtos/[slug].astro)
Depois de renomear, atualize todos os
hrefdo Header, do Footer e dos botões do site para apontar para os novos endereços — senão o visitante cai em página não encontrada.Atualizando os links<a href="/catalogo">Ver a coleção</a> <a href="/produtos/arttools-03mm">Caneta 0.3 mm</a>
Formulário que salva direto na planilha do Google
Sem banco de dados e sem servidor: o formulário do site envia os cadastros para uma planilha do Google Sheets, que funciona como a sua base de contatos.
- 01
Crie o script na planilha (Google Apps Script)
Na sua planilha, abra Extensões → Apps Script, cole o código abaixo e publique comImplantar → Nova implantação → App da Web, com acesso liberado paraqualquer pessoa. Guarde a URL gerada: ela é o endereço para onde o formulário vai enviar os dados.
Código do Apps Script:function doPost(e) { try { const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet(); const data = JSON.parse(e.postData.contents); // Adiciona uma nova linha com os dados recebidos sheet.appendRow([ new Date(), // Coluna A: Data/Hora data.nome || '', // Coluna B: Nome data.telefone || '', // Coluna C: Telefone data.email || '' // Coluna D: E-mail ]); return ContentService.createTextOutput(JSON.stringify({ result: 'success' })) .setMimeType(ContentService.MimeType.JSON); } catch (error) { return ContentService.createTextOutput( JSON.stringify({ result: 'error', error: error.message }) ).setMimeType(ContentService.MimeType.JSON); } } - 02
Monte o formulário no Astro
Três campos — nome, telefone e e-mail — mais o botão de envio e um parágrafo vazio que vai receber as mensagens de sucesso e erro. Repare que cada
namedo input corresponde a uma coluna da planilha: é assim que o script sabe onde colocar cada informação.ContactForm.astro — o formulário:--- // src/components/ContactForm.astro --- <form id="excel-form" class="flex flex-col gap-4 max-w-md mx-auto p-4 border rounded-lg shadow-sm"> <div> <label for="nome" class="block text-sm font-medium mb-1">Nome completo</label> <input type="text" id="nome" name="nome" required placeholder="Ex: Maria Silva" class="border p-2 w-full rounded focus:outline-none focus:ring-2 focus:ring-blue-500" /> </div> <div> <label for="telefone" class="block text-sm font-medium mb-1">Telefone / WhatsApp</label> <input type="tel" id="telefone" name="telefone" required placeholder="Ex: (11) 99999-9999" class="border p-2 w-full rounded focus:outline-none focus:ring-2 focus:ring-blue-500" /> </div> <div> <label for="email" class="block text-sm font-medium mb-1">E-mail</label> <input type="email" id="email" name="email" required placeholder="Ex: maria@email.com" class="border p-2 w-full rounded focus:outline-none focus:ring-2 focus:ring-blue-500" /> </div> <button type="submit" id="submit-btn" class="bg-blue-600 text-white font-medium py-2 px-4 rounded hover:bg-blue-700 transition-colors disabled:opacity-50" > Enviar Cadastro </button> <p id="status-msg" class="text-sm font-semibold hidden text-center mt-2"></p> </form> - 03
Ligue o envio ao Apps Script
No mesmo arquivo, logo abaixo do formulário, entra o script que intercepta o envio e manda os dados para a planilha. Troque
SUA_URL_DO_APPS_SCRIPT_AQUIpela URL que você gerou no passo 1. Omode: "no-cors"é o que permite o envio direto do navegador para o Google.ContactForm.astro — o envio:<script> // INSIRA AQUI A SUA URL GERADA NO GOOGLE APPS SCRIPT const SCRIPT_URL = "SUA_URL_DO_APPS_SCRIPT_AQUI"; const form = document.getElementById("excel-form") as HTMLFormElement; const statusMsg = document.getElementById("status-msg"); const submitBtn = document.getElementById("submit-btn") as HTMLButtonElement; form?.addEventListener("submit", async (e) => { e.preventDefault(); if (submitBtn) submitBtn.disabled = true; if (statusMsg) { statusMsg.textContent = "Enviando dados..."; statusMsg.classList.remove("hidden"); statusMsg.className = "text-sm font-semibold text-center mt-2 text-gray-600"; } const formData = new FormData(form); const data = Object.fromEntries(formData.entries()); try { await fetch(SCRIPT_URL, { method: "POST", mode: "no-cors", headers: { "Content-Type": "application/json", }, body: JSON.stringify(data), }); if (statusMsg) { statusMsg.textContent = "Cadastrado com sucesso!"; statusMsg.className = "text-sm font-semibold text-center mt-2 text-green-600"; } form.reset(); } catch (err) { if (statusMsg) { statusMsg.textContent = "Erro ao enviar. Tente novamente."; statusMsg.className = "text-sm font-semibold text-center mt-2 text-red-600"; } } finally { if (submitBtn) submitBtn.disabled = false; } }); </script>
Conteúdo da Aula
O material e os prompts desta aula estarão disponíveis em breve. Utilize o menu do topo para navegar entre as aulas já publicadas.