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.
Jul 20, 2026
Old site inventory
Survey of pages, posts, categories, tracking and integrations, and project setup.
Jul 25, 2026
Foundation, form and content
Pages rebuilt, a layered-protection form, 301 redirects, real Google reviews and the blog migrated to MDX.
Jul 28, 2026
Site live
Switched to the new hosting with no downtime and no interruption to company email.
Jul 30, 2026
Conversion tracking
Transactional email for the form and conversion events for the form and WhatsApp.
Aug 01, 2026
Hardened protection
Turnstile anti-bot challenge, persistent rate limiting and exact coordinates in the schema for local SEO.
Aug 18, 2026
SEO pass
Metadata and schema reviewed, social icons and a browsable sitemap page.
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.
000 m/interface
- Static pages on the App Router
- Reusable section library
- In-house consent banner
030 m/route
- Contact form route
- 301 redirects
- CSP and security headers
060 m/service
- Zod schema shared by client and server
- Pluggable email provider
- Turnstile verification
090 m/repository
- MDX content validated at build
- Consolidated category map
- Single corporate config
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.
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.
/**
* 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.
/**
* 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.