Pular para o conteúdo
Gustavo Tutorial

Back-end

Tudo que não cabe no navegador: guardar dados, enviar e-mail, esconder uma chave de API, deixar o cliente editar o próprio conteúdo.

Revisão já conversamos sobre isso Novo ainda não vimos 6 partes · 32 tópicos

Nenhum tópico com esse filtro.

PARTE 1

Quando você precisa de servidor

O caminho entre “HTML puro” e “montar um back-end inteiro”. É aqui que moram os pedidos de cliente que hoje você precisa recusar ou improvisar.

Serverless functions

Novo

Em vez de manter um servidor ligado, você escreve uma função isolada que roda só quando alguém chama, e depois desliga. Sem servidor para administrar, sem custo fixo, e a cota gratuita cobre muito mais do que uma landing page precisa — lembrando que, na Vercel, o plano gratuito é só para projeto pessoal: site de cliente vai no plano Pro, ou na Netlify, cujo gratuito aceita projeto comercial.

Na Vercel, basta criar uma pasta api/ ao lado do seu HTML. Cada arquivo ali vira um endereço.

api/contato.js
export default async function handler(req, res) {
  if (req.method !== 'POST') {
    return res.status(405).json({ erro: 'Método não permitido' });
  }

  const { nome, email, mensagem } = req.body;

  if (!nome || !email) {
    return res.status(400).json({ erro: 'Nome e e-mail são obrigatórios' });
  }

  // a chave fica AQUI, no servidor — nunca no HTML
  const chave = process.env.CHAVE_EMAIL;

  // ... envia o e-mail ...

  res.status(200).json({ ok: true });
}

O arquivo acima responde em /api/contato. Do lado do site, você chama com fetch — aquilo da Parte 5.

Por que isso resolve o seu problema principal: a chave de API fica em variável de ambiente, no servidor, invisível para o visitante. É exatamente o que falta numa landing page estática e o motivo de tantas integrações serem impossíveis sem back-end.

Formulário que envia e-mail de verdade

Novo

O pedido mais comum depois do WhatsApp. Existem três caminhos, do mais simples ao mais controlado.

CaminhoComo funcionaCusto / esforço
Serviço de formulário
(Formspree, Web3Forms)
Você aponta o action do form para eles; o e-mail chega na caixa do clienteMinutos. Plano gratuito costuma bastar
Serverless + API de e-mail
(Resend, SendGrid)
Sua função recebe e dispara o e-mailUma tarde. Controle total do texto e do fluxo
Back-end próprioServidor rodando o tempo todoSó se houver outros motivos
o caminho mais rápido
<form action="https://formspree.io/f/SEU_ID" method="POST">
  <label for="email">Seu e-mail</label>
  <input id="email" type="email" name="email" required>

  <label for="msg">Mensagem</label>
  <textarea id="msg" name="mensagem" required></textarea>

  <button type="submit">Enviar</button>
</form>

Sem JavaScript nenhum, e continua funcionando com JS desligado. É difícil bater essa relação esforço/resultado.

Spam é garantido. Todo formulário público recebe robô. Os serviços acima já filtram, mas some um campo escondido (“honeypot”) que humano nunca preenche: se vier preenchido, é robô. Evita a maior parte sem incomodar ninguém com captcha.

Automação sem código

Novo

Ferramentas que conectam serviços por interface visual. Resolvem boa parte dos pedidos de “back-end” sem você programar — e são vendáveis como serviço à parte.

FerramentaCaracterística
Make (ex-Integromat)Visual, poderoso, plano gratuito generoso
ZapierO mais conhecido; mais caro
n8nCódigo aberto, pode rodar no seu próprio servidor

Um fluxo típico que o cliente valoriza: o formulário do site chega no Make; ele grava a linha numa planilha do Google, manda um e-mail de confirmação para quem preencheu, avisa o cliente no WhatsApp e cria o contato no CRM. Nada disso exige uma linha de código.

Oportunidade comercial: a automação normalmente vale mais para o cliente que a página em si — ela economiza o tempo dele toda semana, não uma vez. E é trabalho recorrente, diferente da landing page, que se entrega e acaba.

Headless CMS

Novo

Um painel onde o cliente edita o conteúdo, sem mexer no site. “Headless” significa que ele só guarda e entrega os dados — quem desenha a página continua sendo você.

OpçãoQuando faz sentido
Google SheetsO cliente já sabe usar. Publique a planilha e leia o JSON. Solução mais subestimada que existe
NotionSe o cliente já usa Notion, a API dele serve de CMS
Sanity, Strapi, ContentfulCMS de verdade, com tipos de conteúdo e permissões
Decap CMSGrava direto no repositório Git. Gratuito, sem banco de dados
Antes de montar qualquer CMS, faça a pergunta: com que frequência esse conteúdo muda de verdade? Cardápio que muda toda semana justifica. “Sobre nós” que mudou duas vezes em três anos não justifica — ali, editar o HTML e republicar custa menos para todo mundo.
PARTE 2

Serverless na prática

A primeira função rodando, o segredo que não pode ir para o navegador, e os limites que ninguém conta antes. É a porta de entrada do back-end para quem entrega site estático — e continua sendo site estático, com uma função ao lado.

Anatomia de uma função

NovoJS

Uma função serverless é um arquivo numa pasta com nome combinado. Na Vercel, tudo que está em /api vira um endereço automaticamente: api/orcamento.js responde em seusite.com/api/orcamento.

api/orcamento.js
export default async function handler(req, res) {
  // 1. só aceita o método certo
  if (req.method !== 'POST') {
    return res.status(405).json({ erro: 'Método não permitido' });
  }

  // 2. valida a entrada — NUNCA confie no que chega
  const { nome, fone, mensagem } = req.body || {};
  if (!nome || !fone) {
    return res.status(400).json({ erro: 'Nome e telefone são obrigatórios' });
  }

  // 3. faz o trabalho
  try {
    await enviarEmail({ nome, fone, mensagem });
    return res.status(200).json({ ok: true });
  } catch (e) {
    console.error(e);                       // aparece nos logs da Vercel
    return res.status(500).json({ erro: 'Falha ao enviar' });
  }
}

Do lado do site, é um fetch comum:

no navegador
const r = await fetch('/api/orcamento', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ nome, fone, mensagem })
});
const dados = await r.json();
if (!r.ok) throw new Error(dados.erro);
A regra de ouro do back-end está no passo 2: validação no navegador é conveniência para o usuário, não segurança. Qualquer pessoa manda um POST direto para o seu endereço sem passar pelo formulário. Tudo que chega precisa ser validado de novo, do lado de cá.

Variáveis de ambiente — onde mora o segredo

NovoJS

Esta é a razão número um para existir uma função serverless num site estático: existe uma chave que não pode aparecer no navegador. Chave de envio de e-mail, token de API, senha de banco.

js
// ERRADO — qualquer visitante lê isso com Ctrl+U
const CHAVE = 're_abc123...';
fetch('https://api.resend.com/emails', { headers: { Authorization: 'Bearer ' + CHAVE }});

// CERTO — a chave só existe no servidor
// api/enviar.js
const CHAVE = process.env.RESEND_API_KEY;
.env.local (fica no .gitignore)
RESEND_API_KEY=re_abc123...
DATABASE_URL=postgres://...

# na Vercel: Project Settings → Environment Variables
# e escolha os ambientes: Production, Preview, Development
O detalhe que pega muita gente: em projetos com Vite ou Next, variáveis com prefixo VITE_ ou NEXT_PUBLIC_ são propositalmente embutidas no JavaScript do navegador. Se você colocar a chave secreta com esse prefixo porque "não estava funcionando", você acabou de publicá-la. Prefixo público é só para o que pode ser público — o ID do Analytics, por exemplo.

Enviar e-mail de verdade

RevisãoJS

O caso concreto mais comum. Serviço de envio (Resend, SendGrid, Postmark) mais uma função de dez linhas.

api/orcamento.js
const r = await fetch('https://api.resend.com/emails', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer ' + process.env.RESEND_API_KEY,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    from: 'Site <site@vanessacarvalho.com.br>',   // domínio verificado
    to: 'contato@vanessacarvalho.com.br',
    reply_to: email,                                 // responder vai para o visitante
    subject: `Orçamento — ${nome}`,
    text: `Nome: ${nome}\nWhatsApp: ${fone}\n\n${mensagem}`
  })
});
Dois detalhes que decidem se o e-mail chega ou cai no spam: o from precisa ser um domínio que você verificou no serviço (com os registros SPF e DKIM no DNS) — usar o Gmail do cliente ali faz o e-mail ser rejeitado. E o reply_to com o e-mail de quem preencheu é o que permite ao cliente simplesmente apertar "responder". Sem isso, ele responde para o próprio site.

CORS — o erro que todo mundo encontra

Novo

Uma hora aparece no Console: "blocked by CORS policy". Não é bug, é o navegador protegendo o visitante — ele impede que um site faça requisição para outro domínio sem autorização explícita desse outro domínio.

js
// se a função está no MESMO domínio do site, não existe CORS. Use caminho relativo:
fetch('/api/orcamento')              // sem problema nenhum

// se realmente precisa liberar outro domínio, é na resposta da função:
res.setHeader('Access-Control-Allow-Origin', 'https://site-do-cliente.com.br');
res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
if (req.method === 'OPTIONS') return res.status(200).end();   // o preflight
Nunca use Allow-Origin: * numa função que faz algo — enviar e-mail, gravar dado, gastar sua cota. Você está autorizando qualquer site do mundo a usar sua função às suas custas. Liste o domínio, e só ele.

Limites, custo e partida a frio

Novo
LimitaçãoO que significa
Tempo máximoDezenas de segundos, conforme o plano. Processar vídeo ou gerar relatório grande não cabe.
Partida a frioSe ninguém chamou há um tempo, a primeira chamada demora mais. Invisível num formulário, perceptível numa busca ao vivo.
Sem estadoCada chamada começa do zero. Nada guardado em variável sobrevive. Guardar dado exige banco.
Sem discoEscrever arquivo não persiste. Upload vai para armazenamento externo.
CotaA cota gratuita cobre com folga um site de serviço local. Mas o plano gratuito da Vercel é restrito a uso pessoal e não comercial — site de cliente pede o plano Pro. A Netlify aceita projeto comercial no gratuito.
E o limite que mais importa no seu contexto: função serverless é uma porta aberta na internet. Sem limite por IP, alguém pode chamar mil vezes e esgotar sua cota de e-mail — ou sua fatura. Some o honeypot do formulário, um limite simples de chamadas por IP, e verifique os logs quando algo parecer estranho.

Quando não usar

Novo
PedidoPrecisa de função?Alternativa
Formulário mandando e-mailNão, no começoFormspree ou Web3Forms resolvem sem código
Contato pelo WhatsAppNãoLink wa.me
Formulário virando linha em planilhaNãoMake ou Zapier, sem programar
Esconder chave de API de terceiroSimNão tem alternativa. É o caso clássico.
Gravar ou ler dado de um bancoSimCredencial de banco nunca vai ao navegador
PagamentoSimConfirmação de pagamento tem que ser no servidor
Login e área restritaSimVer a parte de autenticação
O critério é sempre o mesmo: existe algo aqui que o visitante não pode ver ou não pode falsificar? Se a resposta for não, provavelmente dá para resolver sem servidor — e sem servidor é mais barato, mais rápido e dura mais.
PARTE 3

Guardar dado

A partir do momento em que algo precisa continuar existindo depois que a pessoa fecha a aba, você precisa de um lugar para guardar. As opções vão de uma planilha do Google a um banco de verdade — e a escolha errada aqui custa caro só lá na frente.

Você precisa mesmo de banco?

Novo
O dadoOnde guardar
Preferência do visitante (tema, filtro, rascunho)localStorage — fica no navegador dele, não é seu problema
Conteúdo do site que o cliente editaCMS, ou arquivo no repositório
Contatos de formulário, até uns poucos por diaE-mail, ou planilha via automação
Lista de produtos, imóveis, cardápio — que mudaCMS ou banco
Agendamento, pedido, cadastro de usuárioBanco, sem alternativa
Qualquer coisa com dado sensívelBanco, com cuidado extra — e LGPD na conversa
A ordem certa de perguntar: "esse dado precisa ser pesquisado, filtrado ou relacionado com outro?" Se não precisar, quase sempre dá para viver sem banco. Um cardápio de trinta itens cabe num arquivo JSON no repositório, carrega instantaneamente e não tem custo mensal nenhum.

Planilha como banco — quando funciona

Novo

Parece gambiarra e para muito caso de cliente local é a resposta certa: o dono já sabe usar, já tem a conta, consegue editar do celular, e não existe painel novo para ensinar.

Funciona bem paraNão use quando
Receber e organizar contatos do formulárioTiver mais de alguns milhares de linhas
Cardápio, tabela de preços, lista de serviçosVários usuários escrevendo ao mesmo tempo
Cliente que quer ver e mexer sem depender de vocêHouver dado sensível de terceiros
Prova de conceito antes de investir em bancoA leitura for a cada visita do site
O caminho mais simples que existe: Make ou Zapier recebendo o formulário e gravando a linha na planilha, mais uma cópia por e-mail. Zero código, e é vendável como serviço à parte. Para o site ler da planilha, publique-a como CSV e cacheie a resposta — mas saiba que você está dependendo de um serviço que não foi feito para isso.

As opções de banco, sem enrolação

Novo
OpçãoO que éBoa para
SupabasePostgres gerenciado com API pronta, autenticação e armazenamento de arquivoO mais completo para quem vem do front-end. Plano gratuito generoso.
Turso / SQLiteBanco leve, rápido, distribuídoLeitura muito frequente, dado simples
Postgres na Vercel/NeonPostgres puroQuem já sabe SQL e quer controle
FirebaseBanco em tempo real do GoogleTempo real, chat, colaboração
Airtable / NotionPlanilha turbinada com APICliente edita no painel bonito; volume pequeno
Para o seu tipo de projeto, a recomendação honesta é Supabase. Ele traz banco, login e upload de arquivo numa conta só, o painel é usável, e tem plano gratuito que aguenta um site de serviço. Os outros aparecem quando o projeto tem uma exigência específica que o Supabase não atende bem — e aí você já vai saber qual é.

Modelagem mínima — a tabela e o relacionamento

Novo

Você não precisa de um curso de modelagem para um site de serviço. Precisa de três ideias.

sql
-- 1. uma tabela por "coisa" do mundo real
create table clientes (
  id          uuid primary key default gen_random_uuid(),
  nome        text not null,
  telefone    text not null,
  criado_em   timestamptz default now()
);

-- 2. relacionamento = guardar o id do outro
create table agendamentos (
  id          uuid primary key default gen_random_uuid(),
  cliente_id  uuid references clientes(id) on delete cascade,
  data        timestamptz not null,
  servico     text not null,
  status      text default 'pendente'
);

-- 3. índice no que você vai buscar
create index on agendamentos (data);
HábitoPor quê
Sempre um id próprioTelefone e e-mail mudam; id não
Sempre criado_emVocê vai precisar saber "quando", e depois não dá para recuperar
not null no que é obrigatórioO banco vira a última linha de defesa contra dado quebrado
Nome de campo em português ou inglês — mas um sóMetade misturada é o que dá erro às 2h da manhã

Ler e escrever com segurança

NovoJS
api/agendar.js
import { createClient } from '@supabase/supabase-js';

// chave de serviço: SÓ no servidor, nunca no navegador
const db = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_SERVICE_KEY);

export default async function handler(req, res) {
  if (req.method !== 'POST') return res.status(405).end();

  const { nome, telefone, data, servico } = req.body || {};
  if (!nome || !telefone || !data) return res.status(400).json({ erro: 'Dados faltando' });

  const { data: linha, error } = await db
    .from('agendamentos')
    .insert({ nome, telefone, data, servico })
    .select()
    .single();

  if (error) { console.error(error); return res.status(500).json({ erro: 'Falha ao salvar' }); }
  return res.status(200).json({ ok: true, id: linha.id });
}
Injeção de SQL acontece quando você monta a consulta concatenando texto que veio de fora: "select * from x where nome = '" + nome + "'". Alguém digita uma aspa e o resto do comando vira instrução. A defesa é não concatenar nunca — use consulta parametrizada, ou uma biblioteca como o cliente do Supabase, que já faz isso. Vale o mesmo princípio do textContent contra XSS: dado de fora é dado, nunca comando.

Backup e a conversa de LGPD

Novo

No momento em que existe banco, a entrega muda de natureza: você passou a guardar dado pessoal de terceiros, e isso tem consequência prática e contratual.

Obrigação novaComo resolver
Backup automáticoServiços gerenciados já fazem. Confirme a frequência e teste restaurar uma vez.
Exclusão a pedido do titularPrecisa existir um jeito de apagar — mesmo que seja você rodando uma consulta
Prazo de retençãoDefina e cumpra: "contatos apagados após 24 meses"
Acesso restritoChave de serviço só no ambiente do servidor; ninguém mais com acesso ao painel
Onde o dado ficaEscolha a região mais próxima — e saiba dizer ao cliente em que país está
Isso muda o preço e muda o contrato. Site estático que só abre WhatsApp não guarda nada de ninguém. Site com banco de agendamento guarda nome, telefone e horário de dezenas de pessoas — e alguém precisa responder por isso. Deixe claro na proposta quem é o controlador (o cliente) e qual é o seu papel, como está na trilha Negócio.
PARTE 4

Autenticação

O assunto em que improvisar tem o pior retorno possível. Aqui a regra não é "aprenda a fazer": é "saiba o suficiente para escolher quem faz por você, e para não aceitar um pedido que não deveria aceitar".

Por que não escrever login na mão

Novo

Um sistema de login parece simples — usuário, senha, confere — e não é. O que ele precisa ter, de verdade:

ItemPor que é difícil
Senha guardada com hash forteAlgoritmo certo, com sal e custo calibrado. Guardar senha em texto é falha grave — e ainda acontece.
Sessão seguraCookie HttpOnly, Secure, SameSite, com expiração e renovação
Recuperação de senhaToken de uso único, com prazo, que não revela se o e-mail existe
Limite de tentativasSem isso, força bruta acha a senha fraca de alguém
Verificação de e-mailSenão qualquer um cria conta com o e-mail de outro
Segundo fatorEsperado hoje em qualquer coisa séria
Cada um desses itens tem um jeito sutil de dar errado, e o erro não aparece em teste — aparece quando alguém explora. Provedores prontos resolvem os seis, são auditados, e custam zero para o volume de um site de serviço. Escrever isso na mão é assumir um risco que não é seu de assumir.

Os provedores prontos

Novo
OpçãoDestaqueQuando
Supabase AuthVem junto do banco, com e-mail, link mágico e login socialSe você já usa Supabase, é a escolha óbvia
ClerkTelas prontas e bonitas, integração rápidaQuando você quer resolver em uma tarde
Auth0Completo e corporativoExigência de empresa maior
Firebase AuthSólido, bem documentadoProjeto já no ecossistema Google

E o caminho que quase sempre é melhor para cliente de serviço local: link mágico. A pessoa digita o e-mail, recebe um link, clica e está dentro. Sem senha para criar, sem senha para esquecer, sem tela de recuperação.

js
const { error } = await supabase.auth.signInWithOtp({
  email,
  options: { emailRedirectTo: 'https://site.com.br/painel' }
});
// pronto. sem senha, sem hash, sem recuperação para implementar.

Sessão, token e o que fica onde

NovoJS

Depois do login, o navegador precisa provar quem é a cada requisição. Duas formas, e a diferença importa.

Cookie de sessãoToken no localStorage
Vai junto na requisiçãoAutomaticamenteVocê precisa anexar
JavaScript consegue lerNão, se for HttpOnlySim — e isso é o problema
Risco de XSS roubarBaixoAlto: um script injetado leva o token
RecomendaçãoPrefiraSó quando não há alternativa
js
res.setHeader('Set-Cookie', [
  `sessao=${token}`,
  'HttpOnly',      // JavaScript não lê
  'Secure',        // só por HTTPS
  'SameSite=Lax',  // não vai em requisição de outro site
  'Path=/',
  'Max-Age=604800' // 7 dias
].join('; '));
E o erro conceitual mais comum: esconder um botão no front-end não é proteger nada. A verificação tem que estar na função que devolve o dado. Se o endereço /api/pedidos devolve a lista para quem chamar, não importa que a tela de login exista — basta abrir o endereço direto.

O que realmente pede login — e o que não pede

Novo

Clientes pedem "área do cliente" com frequência, e na maioria das vezes o que eles querem não precisa de login nenhum.

O cliente pedeO que ele querSolução mais simples
"Área do cliente para ver o andamento da obra"Mandar foto e statusGrupo de WhatsApp, ou link secreto por projeto
"Login para baixar o catálogo"Capturar contatoFormulário que libera o PDF
"Painel para eu ver os orçamentos"Ver os contatos recebidosPlanilha ou e-mail
"Cada cliente com senha para ver o projeto dele"Privacidade dos arquivosPasta compartilhada no Drive
"Agendamento online com histórico"Isso simLogin de verdade, ou ferramenta pronta de agendamento
A pergunta que resolve a reunião: "o que muda para você se isso tiver senha?". Se a resposta for "fica mais profissional", não precisa de login — precisa de design. Se for "os clientes não podem ver o projeto uns dos outros", aí sim existe um requisito real, e ele muda prazo e preço de forma significativa.

Pagamento: nunca reinvente

Novo

Regra curta: dado de cartão nunca passa pelo seu código. Nem para "só validar", nem em campo do seu formulário. Quem processa é o gateway, na página dele ou num componente dele.

CaminhoComo funcionaQuando
Link de pagamentoVocê gera um link no painel e mandaServiço sob orçamento. Zero código — e resolve a maioria dos casos.
Checkout hospedadoO botão leva para a página do gatewayProduto de preço fixo, infoproduto
Integração completaVocê cria a cobrança por API e recebe a confirmação por webhookLoja, assinatura, volume
Se chegar na integração completa, o ponto crítico é um só: confirme o pagamento pelo webhook do gateway, no servidor — nunca pelo que o navegador diz ao voltar. A URL de retorno pode ser acessada por qualquer um. No Brasil os nomes que você vai encontrar são Mercado Pago, Pagar.me, Asaas e Stripe; todos têm link de pagamento, que é por onde começar.
PARTE 5

O cliente editando sozinho

Uma hora ele pergunta: "e se eu quiser mudar esse texto?". A resposta define se você ganha um contrato de manutenção, um painel para manter, ou um cliente que quebra o próprio site no sábado.

A pergunta antes da ferramenta

Novo

Antes de escolher CMS, descubra o que muda e com que frequência. A resposta quase sempre é bem menor do que o pedido sugere.

Frequência realSolução honesta
Duas ou três vezes por anoNenhuma. Está incluído na manutenção — e é mais rápido você fazer.
Uma vez por mês, texto e fotoEdição direta no GitHub, ou CMS leve
Toda semana, itens de uma listaCMS de verdade, ou planilha
Vários por dia, com equipeCMS completo — e isso é outro projeto, com outro preço
O custo escondido que ninguém calcula: um painel é uma coisa a mais para manter, atualizar, e ensinar alguém a usar. Cliente que edita três vezes por ano e tem painel esquece a senha, esquece como faz, e te liga assim mesmo — só que agora você também precisa manter o painel. Para muita gente, "me manda o texto que eu troco em dez minutos" é melhor serviço e melhor negócio para os dois.

Os tipos de CMS

Novo
TipoComo funcionaCusto real
Tradicional
WordPress
Painel e site na mesma aplicação, com bancoAtualização constante, risco de invasão, hospedagem que aguente
Headless
Sanity, Strapi, Contentful
Só guarda e entrega o conteúdo; a página continua suaMais uma conta, mais uma mensalidade acima de certo uso
Baseado em Git
Decap, TinaCMS
O painel edita arquivos do repositório; salvar vira um commitZero de infraestrutura. Precisa de build.
Planilha / Notion
Sheets, Airtable
O cliente edita onde já sabe mexerQuase zero. Limite de volume.
GitHub diretoO cliente edita o HTML na interface do GitHubZero. Exige um cliente destemido e um trecho bem isolado.
Para o seu contexto, a ordem de preferência costuma ser: nada (manutenção), depois planilha, depois CMS baseado em Git, e só então headless. WordPress só quando o cliente exige por nome — e aí a conversa passa a ser sobre atualização de segurança, que é responsabilidade recorrente de alguém.

CMS que salva no Git

Novo

É a opção mais elegante para site estático: o cliente abre um painel comum, edita, clica em salvar — e o que acontece por baixo é um commit no repositório, que dispara o deploy automático.

config.yml — Decap CMS
backend:
  name: github
  repo: JaumNf/site-vanessa
  branch: main

media_folder: "img/uploads"

collections:
  - name: "servicos"
    label: "Serviços"
    folder: "conteudo/servicos"
    create: true
    fields:
      - { name: titulo,  label: "Título",   widget: string }
      - { name: resumo,  label: "Resumo",   widget: text }
      - { name: foto,    label: "Foto",     widget: image }
      - { name: preco,   label: "A partir de", widget: number, required: false }

As vantagens somam: não existe banco para manter, o histórico de tudo que o cliente mudou está no Git, e desfazer uma edição ruim é o mesmo git revert de sempre. Se o CMS sair do ar, o site continua no ar — o conteúdo está nos arquivos.

O preço: isso exige uma etapa de build para transformar o conteúdo em HTML — o que nos leva de volta à conversa da trilha Fluxo de trabalho. Com Astro ou Eleventy no projeto, o encaixe é natural. Em HTML puro escrito à mão, não encaixa.

Entregar o painel sem criar dependência

Novo

Painel entregue sem preparo volta como suporte eterno. Quatro coisas que resolvem quase tudo:

Grave um vídeo curto de tela. Cinco minutos mostrando as três operações que ele vai fazer — trocar um texto, trocar uma foto, publicar. Ele assiste de novo daqui a seis meses, quando esquecer; você não repete a explicação.

Limite o que dá para editar. Campo definido, não editor livre. Cliente com liberdade total cola texto do Word com formatação embutida e quebra o layout — e o conserto é seu.

Trave o que não pode mudar. Estrutura, menu, rodapé e SEO ficam fora do painel. O que ele edita é conteúdo, não arquitetura.

Mostre como desfazer. Se é CMS em Git, o histórico existe e você pode voltar. Saber disso dá segurança para ele mexer — e é justamente o medo de quebrar que faz o painel ficar sem uso.

O sinal de que deu certo: passaram três meses e o cliente editou alguma coisa sem te chamar. Se ninguém nunca usou o painel, você construiu e mantém algo que não serviu para nada — e o correto é conversar sobre tirar.

A tabela de decisão

Novo
CenárioEscolha
Landing page de serviço, conteúdo estávelSem CMS. Alterações via manutenção.
Site com portfólio que cresce devagarCMS em Git, se já houver build; senão, manutenção
Cardápio, tabela de preços que mudaPlanilha lida pelo site, com cache
Blog com publicação frequenteCMS em Git ou headless
Catálogo grande, com busca e filtroHeadless com banco
Equipe publicando, com aprovaçãoCMS completo — outro projeto, outro orçamento
Cliente exige WordPress por nomeWordPress, com contrato de atualização de segurança
E a resposta para a pergunta original, a que ele fez na reunião: "dá para você mesma editar, sim — e eu te mostro as opções. Só me diz primeiro o que você imagina mudando, e de quanto em quanto tempo." Metade das vezes, a própria resposta dele encerra o assunto.
PARTE 6

O que existe e você não vai usar tão cedo

Fecha o mapa. Cada um destes é legítimo, resolve um problema real, e não aparece no seu trabalho hoje. Saber o nome, o problema que resolve e o sinal de que chegou a hora vale mais do que estudar agora — e evita tanto o susto quanto a adoção precoce.

TypeScript

NovoJS

JavaScript com tipos declarados. O editor passa a saber o formato dos seus dados e avisa do erro enquanto você escreve, em vez de o erro aparecer no navegador do cliente.

ts
type Servico = { titulo: string; preco: number; ativo: boolean };

function formatar(s: Servico) {
  return `${s.titulo} — R$ ${s.preco.toFixed(2)}`;
}

formatar({ titulo: 'Reforma', preco: '8000', ativo: true });
//                            ^ erro apontado aqui, antes de rodar
O sinal de que chegou a hora: você tem um projeto com mais de uns poucos milhares de linhas, ou mais de uma pessoa mexendo, ou dados vindo de uma API com muitos campos. Em arquivo único de landing page, o custo de configurar supera o ganho. Um meio-termo barato: // @ts-check no topo de um .js mais tipos por comentário JSDoc — o VS Code já avisa, sem build nenhum.

Testes automatizados

NovoJS

Você já viu o mais útil deles na trilha Qualidade: o teste de fumaça. Os outros dois níveis existem e aparecem em projeto maior.

TipoTestaQuando aparece
Unitário (Vitest, Jest)Uma função isolada: "a máscara de CPF formata certo?"Quando existe lógica de verdade, não só layout
IntegraçãoPeças conversando: "a função salva no banco?"Com back-end próprio
Ponta a ponta (Playwright)O fluxo do usuário no navegador realJá vale hoje, na versão fumaça
um teste unitário, para ver a cara
import { expect, test } from 'vitest';
import { mascaraTelefone } from './mascaras.js';

test('formata celular com nove dígitos', () => {
  expect(mascaraTelefone('67999998888')).toBe('(67) 99999-8888');
});
test('ignora letras', () => {
  expect(mascaraTelefone('67abc99999')).toBe('(67) 9999-9');
});
O valor do teste não é achar o erro de hoje — é garantir que o conserto de hoje não volte daqui a três meses. Por isso ele só se paga em código que você vai manter por muito tempo.

PWA e instalação

Novo

Um site que o visitante instala na tela inicial do celular, abre sem a barra do navegador e funciona offline. São dois arquivos: um manifesto e um service worker.

Faz sentidoNão faz sentido
Ferramenta que a pessoa usa toda semanaLanding page que se visita uma vez
Painel interno de equipeSite institucional
Catálogo consultado em local sem sinalPágina de captura de contato
O risco de ligar isso sem precisar: service worker guarda a página em cache de forma agressiva. Você publica a correção, e o visitante continua vendo a versão antiga — às vezes por dias. É uma das fontes mais difíceis de diagnosticar do "mas eu já corrigi isso". Em site de cliente, só ligue se houver um motivo claro.

Vários idiomas

NovoHTML

Aparece quando o cliente atende estrangeiro — pousada, turismo, comércio exterior, clínica que recebe de fora. O erro comum é traduzir com JavaScript: o buscador indexa uma versão só, e você perde o ganho principal.

html
<!-- uma URL por idioma, HTML servido pronto em cada uma -->
site.com.br/          → português
site.com.br/en/       → inglês
site.com.br/es/       → espanhol

<!-- e no head de TODAS elas, apontando umas para as outras -->
<link rel="alternate" hreflang="pt-BR" href="https://site.com.br/">
<link rel="alternate" hreflang="en"    href="https://site.com.br/en/">
<link rel="alternate" hreflang="x-default" href="https://site.com.br/">
Duplicar arquivo funciona para duas ou três páginas. Acima disso, é o caso clássico em que um gerador estático se paga: o conteúdo fica num arquivo por idioma e o build monta as versões. Cuidado com o óbvio que dá errado: número de telefone, moeda, formato de data e horário de atendimento também mudam por idioma.

Saber que quebrou antes do cliente

Novo

Este é o único da parte que vale montar já — e é barato. Hoje, se o site do cliente sair do ar às 22h de sexta, você descobre quando ele te manda mensagem no domingo.

CamadaFerramentaCusto
Site está no ar?UptimeRobot, Better Stack — checam de minuto em minuto e avisamGratuito no plano básico
Deu erro de JavaScript em algum visitante?SentryGratuito até um volume alto
Função serverless falhou?Logs da Vercel, com alertaIncluído
Formulário parou de chegar?Um envio de teste automático por semanaUm script seu
O argumento comercial: monitoramento é um item excelente no pacote de manutenção. "Eu sou avisado em um minuto se o site sair do ar" é concreto, o cliente entende na hora, e te custa cinco minutos de configuração uma vez só. Descobrir antes dele e avisar antes de ele perguntar é o que faz a manutenção parecer barata.

O mapa completo — quando cada coisa aparece

Novo
Você precisa quando...AssuntoOnde estudar
SempreHTML, CSS, JS, Git, DevTools, imagem, acessibilidadeHTML puro, Fluxo, Qualidade
Há uma chave que não pode ir ao navegadorFunção serverlessParte 2 daqui
Algo precisa persistir e ser consultadoBanco de dadosParte 3 daqui
Uma pessoa não pode ver o dado de outraAutenticaçãoParte 4 daqui
O cliente edita toda semanaCMSParte 5 daqui
O projeto passa de alguns milhares de linhasTypeScriptAqui, e o básico em HTML puro
Você mantém o mesmo código por anosTestesAqui e em Qualidade
O site tem 5+ páginas que você atualiza sempreGerador estáticoFrameworks e Fluxo
A interface tem muito estado que mudaReact ou similarFrameworks
O cliente atende estrangeiroVários idiomasAqui
Você tem cliente em produçãoMonitoramentoAqui
A leitura que importa da tabela: a primeira linha é o que você usa todo dia, e é quase tudo. As outras esperam um sinal específico. Estudar o que está embaixo antes do sinal chegar é o jeito mais comum de passar meses aprendendo ferramenta e nenhum mês entregando projeto — e o mercado paga por projeto entregue.