Pular para o conteúdo
KW
Voltar aos projetos

amostra 01/em produção

Visa Contabilidade

Um site institucional que saiu do WordPress sem perder o que já ranqueava.

  • Next
  • MDX
  • Zod
  • Resend

01

Contexto

A Visa Contabilidade atua em Cascavel, no Paraná, desde 1985. Ao encerrar o contrato com a agência anterior, o escritório precisava migrar o site sem perder a presença que já tinha nas buscas.

O pedido era manter a identidade visual e deixar o site leve, com SEO local forte e preparado para buscas feitas por inteligência artificial.

02

Ponto de partida

Antes de escrever qualquer linha, o site antigo foi inventariado pela API pública do WordPress.

12

páginas institucionais

73

posts no blog, de 2020 a 2026

79

categorias de blog

0

dados estruturados ou meta descriptions

03

Linha do tempo

38 commits no histórico do projeto. Estes são os marcos.

  1. 20 de jul. de 2026

    Inventário do site antigo

    Levantamento de páginas, posts, categorias, rastreamento e integrações, e criação do projeto.

  2. 25 de jul. de 2026

    Base, formulário e conteúdo

    Páginas reconstruídas, formulário com camadas de proteção, redirecionamentos 301, avaliações reais do Google e o blog migrado para MDX.

  3. 28 de jul. de 2026

    Site no ar

    Troca para a nova hospedagem sem tirar o site do ar e sem interromper o e-mail corporativo.

  4. 30 de jul. de 2026

    Medição de conversões

    E-mail transacional do formulário e eventos de conversão para formulário e WhatsApp.

  5. 01 de ago. de 2026

    Proteção reforçada

    Desafio anti-bot com Turnstile, limite de requisições persistente e coordenadas exatas no schema para SEO local.

  6. 18 de ago. de 2026

    Rodada de SEO

    Metadados e schema revisados, ícones sociais e um mapa do site navegável.

  7. 08 de set. de 2026

    Autoridade e acessibilidade

    Registro no conselho de contabilidade no schema e ajustes de acessibilidade também lidos por agentes de IA.

04

Mapa de decisões

Reconstruído a partir do histórico de commits, do README e da documentação do projeto.

01

O blog virou código

Os 73 posts foram convertidos para MDX dentro do repositório por um script. O conteúdo deixou de depender do banco do WordPress e ganhou histórico e revisão.

02

Uma fonte única de verdade

Telefone, endereço, redes e navegação moram num único arquivo. Cabeçalho, rodapé, schema e metadados leem dele, então uma mudança acontece num lugar só.

03

Formulário como pilha de defesa

Cada envio passa por origem, limite de requisições, tamanho, validação, anti-bot e proteção contra injeção antes de virar e-mail.

04

Nenhum link perdido

36 redirecionamentos 301, cruzados com os dados do Search Console: todas as páginas que recebiam cliques continuaram respondendo.

05

SEO construído do zero

Dados estruturados para o escritório, perguntas frequentes, trilhas de navegação, posts e avaliações, com sitemap e robots liberando os rastreadores de IA.

06

Categorias com sentido

79 categorias foram consolidadas em 9 por um mapa não destrutivo. O conteúdo original ficou intacto.

05

Arquitetura

As mesmas cinco camadas desta página, aplicadas ao projeto.

  1. 000 m/interface

    • Páginas estáticas no App Router
    • Biblioteca de seções reutilizáveis
    • Banner de consentimento próprio
  2. 030 m/rota

    • Rota do formulário de contato
    • Redirecionamentos 301
    • CSP e cabeçalhos de segurança
  3. 060 m/serviço

    • Schema Zod compartilhado entre cliente e servidor
    • Provedor de e-mail plugável
    • Verificação do Turnstile
  4. 090 m/repositório

    • Conteúdo em MDX validado no build
    • Mapa de categorias consolidadas
    • Configuração institucional única
  5. 120 m/persistência

    • Arquivos versionados no repositório
    • Limite de requisições no Upstash Redis

06

Código

Trechos reais do repositório público.

O formulário como pilha de defesa

A ordem importa: as verificações mais baratas vêm primeiro, e cada uma devolve um erro genérico que não revela a regra para quem está atacando.

app/api/contato/route.tsts
export async function POST(request: Request): Promise<Response> {
  // CSRF: só aceita requisições do próprio site.
  if (!isSameOrigin(request)) {
    return NextResponse.json({ ok: false, error: "forbidden" }, { status: 403 });
  }

  // Rate limit por IP (durável via Upstash se configurado, in-memory senão).
  const ip = clientIp(request.headers);
  const rl = await contactRateLimiter.check(`contato:${ip}`);
  if (!rl.success) {
    return NextResponse.json(
      { ok: false, error: "rate_limited" },
      { status: 429, headers: { "Retry-After": String(rl.retryAfter) } },
    );
  }

  // ... parse com teto de tamanho (evita payload gigante)

  // Validação autoritativa no servidor.
  const parsed = contactSchema.safeParse(body);
  if (!parsed.success) {
    return NextResponse.json({ ok: false, error: "invalid" }, { status: 422 });
  }
  const data = parsed.data;

  // Anti-spam: honeypot + timing. Bot recebe "sucesso" falso (não vaza a regra).
  if (looksLikeBot({ honeypot: data.company, renderedAt: data.renderedAt })) {
    return NextResponse.json({ ok: true, id: "ignored" }, { status: 200 });
  }

  // ... Turnstile, proteção contra injeção de cabeçalho e envio
}

Anti-spam sem serviço de terceiros

Um campo invisível que só robô preenche e um tempo mínimo de preenchimento. Resolve boa parte do spam sem depender de nenhum serviço externo.

lib/security/request-guards.tsts
/**
 * Anti-spam sem terceiros:
 * - honeypot: campo invisível que só bot preenche.
 * - timing: formulário enviado rápido demais (< limiar) = bot.
 */
export function looksLikeBot(input: {
  honeypot?: unknown;
  renderedAt?: unknown;
  minMs?: number;
}): boolean {
  if (typeof input.honeypot === "string" && input.honeypot.trim() !== "") return true;

  const minMs = input.minMs ?? 2500;
  const renderedAt = Number(input.renderedAt);
  if (Number.isFinite(renderedAt) && renderedAt > 0) {
    if (Date.now() - renderedAt < minMs) return true;
  }
  return false;
}

CSRF barrado pela origem

Um POST forjado em outro domínio chega com outra origem e é recusado antes de qualquer processamento. Sem origem nenhuma, também é recusado.

lib/security/request-guards.tsts
/**
 * CSRF básico: só aceita requisições cuja Origin/Referer bate com o próprio site.
 * Bloqueia POST forjado a partir de outro domínio.
 */
export function isSameOrigin(request: Request): boolean {
  const origin = request.headers.get("origin");
  const referer = request.headers.get("referer");

  const allowed = new Set<string>([siteConfig.url]);

  // ... origens de desenvolvimento liberadas fora de produção

  const candidate = origin ?? (referer ? safeOrigin(referer) : null);
  if (!candidate) return false;
  return allowed.has(candidate.replace(/\/$/, ""));
}

07

Resultado

108

páginas geradas como HTML estático

36

redirecionamentos 301

100%

das páginas com cliques preservadas

9

avaliações reais do Google com schema

Estado atual

Em produção desde 28 de julho de 2026, com manutenção contínua de SEO.