Skip to content
KW
Back to projects

sample 01/in production

Visa Contabilidade

A corporate site that left WordPress without losing what already ranked.

  • Next
  • MDX
  • Zod
  • Resend

01

Context

Visa Contabilidade has served Cascavel, in southern Brazil, since 1985. When it ended its contract with the previous agency, the firm needed to migrate its site without losing its existing search presence.

The brief was to keep the visual identity and make the site lightweight, with strong local SEO and ready for AI-driven search.

02

Starting point

Before writing a single line, the old site was inventoried through the public WordPress API.

12

corporate pages

73

blog posts, from 2020 to 2026

79

blog categories

0

structured data or meta descriptions

03

Timeline

38 commits in the project history. These are the milestones.

  1. Jul 20, 2026

    Old site inventory

    Survey of pages, posts, categories, tracking and integrations, and project setup.

  2. Jul 25, 2026

    Foundation, form and content

    Pages rebuilt, a layered-protection form, 301 redirects, real Google reviews and the blog migrated to MDX.

  3. Jul 28, 2026

    Site live

    Switched to the new hosting with no downtime and no interruption to company email.

  4. Jul 30, 2026

    Conversion tracking

    Transactional email for the form and conversion events for the form and WhatsApp.

  5. Aug 01, 2026

    Hardened protection

    Turnstile anti-bot challenge, persistent rate limiting and exact coordinates in the schema for local SEO.

  6. Aug 18, 2026

    SEO pass

    Metadata and schema reviewed, social icons and a browsable sitemap page.

  7. Sep 08, 2026

    Authority and accessibility

    Accounting council registration in the schema and accessibility fixes that AI agents also read.

04

Decision map

Reconstructed from the commit history, the README and the project documentation.

01

The blog became code

All 73 posts were converted to MDX inside the repository by a script. Content stopped depending on the WordPress database and gained history and review.

02

A single source of truth

Phone, address, social links and navigation live in one file. Header, footer, schema and metadata read from it, so a change happens in one place.

03

The form as a defense stack

Every submission goes through origin, rate limit, size, validation, anti-bot and injection checks before it becomes an email.

04

No link left behind

36 permanent redirects, cross-checked against Search Console data: every page that received clicks kept answering.

05

SEO built from scratch

Structured data for the firm, FAQs, breadcrumbs, posts and reviews, with a sitemap and robots rules that welcome AI crawlers.

06

Categories that make sense

79 categories were consolidated into 9 through a non-destructive map. The original content stayed intact.

05

Architecture

The same five layers as this page, applied to the project.

  1. 000 m/interface

    • Static pages on the App Router
    • Reusable section library
    • In-house consent banner
  2. 030 m/route

    • Contact form route
    • 301 redirects
    • CSP and security headers
  3. 060 m/service

    • Zod schema shared by client and server
    • Pluggable email provider
    • Turnstile verification
  4. 090 m/repository

    • MDX content validated at build
    • Consolidated category map
    • Single corporate config
  5. 120 m/persistence

    • Files versioned in the repository
    • Rate limits in Upstash Redis

06

Code

Real excerpts from the public repository.

The form as a defense stack

Order matters: the cheapest checks run first, and each returns a generic error that reveals nothing about the rule to an attacker.

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 with no third party

An invisible field only bots fill in, plus a minimum fill-in time. It stops much of the spam without relying on any external service.

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 stopped by origin

A POST forged from another domain arrives with a different origin and is refused before any processing. With no origin at all, it's refused too.

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

Outcome

108

pages generated as static HTML

36

permanent redirects

100%

of pages with clicks preserved

9

real Google reviews with schema

Current status

In production since July 28, 2026, with ongoing SEO maintenance.