MosKomDez — site de empresa de dedetização
Site em operação da MosKomDez, empresa de desinfecção, dedetização e desratização: home, 27 páginas de serviços, contatos e política de privacidade, além de um painel administrativo próprio. Substituição completa de uma landing page feita na Tilda — com migração de conteúdo, limpeza dos problemas técnicos do original e preservação do SEO. O diferencial: um único site físico atende 80 versões de cidade em subdomínios wildcard — Moscou no domínio principal e 79 cidades da região de Moscou, sem uma única cópia dos arquivos. Os pedidos vão ao mesmo tempo para o amoCRM do cliente e para o Telegram. Tudo escrito em PHP puro, sem CMS e sem banco de dados, roda em hospedagem compartilhada comum e não exige nenhum build no servidor.
O que era precisoO site estava no Tilda, esbarrava nos limites da plataforma e não permitia páginas separadas para as cidades da região.
O que ficou prontoUm único site atende 80 versões por cidade, os pedidos vão para o amoCRM e o Telegram, e as posições de busca sobreviveram à migração.
Arquitetura multicidade
O cliente pediu páginas de cidade para toda a região de Moscou. Copiar o site 80 vezes estava fora de questão: o conteúdo é editado pelo painel e as cópias se desencontrariam de imediato.
- O registro de cidades é um único JSON: o slug (que também é o subdomínio), o nome e quatro formas de declinação, além das flags “principal”, “ativa” e “indexável”. O mesmo arquivo alimenta a lista de cidades na página e o seletor de versões.
- Os nomes das cidades entram por placeholders — {CITY}, {CITY_IN}, {CITY_TO} e outros — substituídos no HTML já pronto por um callback do buffer de saída. Assim funcionam em todos os lugares de uma vez: nos templates, nos textos vindos do painel, em alt e title, nos campos ocultos dos formulários e nos dados estruturados. Não existe onde esquecer de ligar a substituição em um bloco novo.
- Personalizados: title, description, keywords, títulos H1–H3, o texto principal e o de SEO, o FAQ, as legendas das imagens e os formulários. Deliberadamente não personalizados: o endereço do escritório e as localizações reais dos exemplos de trabalho.
- A análise do host acontece antes de qualquer saída: um subdomínio desconhecido devolve um 404 honesto, e o subdomínio da cidade principal redireciona com 301 para o domínio principal, para não gerar duplicatas.
- Canonical e og:url são calculados a partir do host atual, então continuam corretos no subdomínio sem ajuda. O robots.txt é gerado por PHP: a linha Sitemap aponta para o host atual e, nas versões de cidade, não existe.
- Toda a lógica de subdomínios está trancada em um único arquivo — o resto do código nem sabe que ela existe.
- O painel administrativo devolve 404 nos subdomínios: 79 portas de entrada extras para o login são desnecessárias.
Conteúdo e painel administrativo próprio
O conteúdo é volumoso — 27 páginas de serviços, nove tabelas de preços, 80 cidades — e tudo fica em JSON, editado por um painel feito à mão.
- Editor da página de serviço em abas: SEO, hero (título, checklist, imagem, preço), textos, tabela de preços composta por várias tabelas, FAQ e o conjunto de blocos da página — galeria, métodos, certificados, quiz e os demais podem ser desligados serviço a serviço.
- Telas separadas: hero da home, tabela de preços da home, slider de serviços, exemplos de trabalho “antes/depois”, cidades, configurações e uma lixeira que restaura serviços excluídos.
- Toda gravação segue o mesmo caminho: validação → backup da versão anterior (10 cópias por arquivo) → releitura de controle do que será gravado → arquivo temporário → rename atômico. Uma requisição interrompida não deixa JSON quebrado, e o erro volta como texto legível, não como um 500.
- Os textos passam por uma whitelist de tags com remoção total de atributos: um copiar-e-colar do Word não quebra o layout nem contrabandeia um script.
- Upload de fotos: o arquivo é aceito com verificação de tipo, recortado e convertido para WebP no servidor via GD.
- A tabela de cidades não tem, de propósito, a coluna “Principal”: o servidor pega essa flag do arquivo em uso e ignora a que veio no envio, e a linha da cidade principal não pode ser excluída nem desativada — o cliente não deve conseguir reatribuir o domínio principal por acidente.
Pedidos, quiz e integração com o amoCRM
São seis pontos de entrada de pedidos no site: a faixa promocional, o formulário do hero, “designar um especialista”, uma janela modal, dois formulários na página de contatos e o quiz-calculadora.
- Um quiz de quatro passos mais a tela final. Na página de serviço, a opção correspondente do passo “problema” já vem marcada — a pessoa acabou de ler o título, não faz sentido obrigá-la a clicar no óbvio. O cliente envia apenas os índices das respostas; os textos o servidor pega do próprio JSON, então não dá para mandar uma string arbitrária.
- A cidade do pedido vem do host, e não de um campo do formulário — não dá para falsificar.
- Proteção do endpoint: honeypot, validação de telefone e consentimento no servidor, corte dos campos por número de caracteres e limite de 5 pedidos por IP a cada 10 minutos, num arquivo sob flock.
- Integração com o amoCRM real do cliente, sem ambiente de teste: o pedido cai na caixa “Não organizados” do funil certo — pessoas físicas e jurídicas são separadas pelo primeiro passo do quiz. As respostas do quiz são mapeadas em campos reais do CRM, as marcações de anúncio em campos de serviço da API, e o restante entra como observação.
- A busca de duplicatas por telefone é obrigatória, não opcional: a telefonia e um serviço de callback já escrevem no mesmo CRM. A busca do amo é textual e varre todos os campos de uma vez, por isso procuramos pelos últimos dez dígitos do número e depois conferimos o contato encontrado — caso contrário o pedido gruda no cartão de outra pessoa.
- Nenhum ID de campo foi escrito à mão: o mapa de correspondências é gerado por script a partir de um dump da conta, verificado por um comando à parte, e a montagem do pedido nos campos é testada localmente, sem nenhuma chamada de rede.
- O Telegram ficou como segundo canal a pedido do cliente: o pedido é considerado recebido se pelo menos um canal funcionou e, se nenhum funcionou, ele é anexado a um log privado em disco. Não se pode perder um lead: a pessoa não escreve uma segunda vez.
- Webhook reverso: quando o negócio chega à etapa final, uma mensagem vai para um grupo do Telegram com a cidade, o problema, os contatos e o link do negócio.
- As marcações de anúncio do primeiro acesso (utm, gclid, yclid, referrer) ficam num cookie no domínio base — ele sobrevive ao redirecionamento geográfico para o subdomínio, então a origem não é sobrescrita e o tráfego pago não vira acesso direto.
Detecção da cidade do visitante
Um requisito à parte do cliente: a pessoa deve cair direto na versão da própria cidade.
- O navegador é consultado por geolocalização; em caso de recusa ou timeout, a cidade é resolvida por IP — os dois caminhos passam pela DaData.
- O nome é normalizado (a cidade do serviço e a do registro são comparadas sem o prefixo “g.”, sem diferenciar maiúsculas e sem a letra “ё”) e procurado no registro; cidade fora da lista significa permanecer em Moscou.
- Cidade reconhecida significa ir para o subdomínio dela pelo mesmo caminho, seguido de um pop-up: “Precisa de tratamento em …?” — “Sim” permanece, “Não” abre a lista de cidades em tela cheia, com busca ao vivo.
- O resultado e a confirmação ficam em cookies no domínio base, para que os subdomínios enxerguem uns aos outros: a detecção roda uma única vez por visitante.
- A cota gratuita do serviço é de 10 000 requisições por dia, então o endpoint mantém o próprio limite — 20 detecções por IP por hora, com o contador num arquivo sob flock.
SEO
- Metadados únicos para cada um dos 27 serviços, personalizados por cidade.
- JSON-LD: LocalBusiness com telefones, endereço decomposto em partes e área de atendimento da cidade atual, além de FAQPage — mas só onde o bloco de perguntas realmente aparece na página.
- O sitemap.xml é montado a partir do índice de serviços; a data de modificação vem do arquivo de conteúdo e se atualiza sozinha a cada edição pelo painel.
- URLs planas e legíveis, com o endereço canônico garantido por um 301 que preserva as marcações UTM.
- As versões de cidade estão fechadas à indexação por meta tag e cabeçalho HTTP, enquanto o robots.txt não as bloqueia — o robô precisa entrar e ver o noindex. Liberar uma cidade para a busca é uma chave no painel.
- Open Graph e Twitter card em todas as páginas, e uma página 404 com conteúdo útil e noindex.
Performance e acessibilidade
PageSpeed Insights, domínio de produção, agosto de 2026. Página de serviço: Performance 100, Acessibilidade 100, Práticas recomendadas 96, SEO 100 no mobile, e 99/100/96/100 no desktop. Home: 92/100/88/100 no mobile.
- A velocidade vem da maquetação e dos assets, não da infraestrutura: sem CDN, sem camada de cache, sem bundlers no servidor — hospedagem compartilhada comum.
- Roboto self-hosted no lugar do Google Fonts eliminou cerca de 2 segundos de render-blocking e uma cadeia de cinco requisições externas: três woff2 variáveis por subset, com cirílico e latino em preload.
- Minificação própria e versionamento de assets pela data de modificação do arquivo: CSS 91,6 → 54,6 KB (10,2 KB pela rede), JS 33,1 → 14,3 KB (4,7 KB). Se o minificado estiver desatualizado, o original é servido — o site não quebra.
- Todas as imagens em WebP com dimensões explícitas, srcset e fetchpriority na imagem de LCP, lazy loading no restante e content-visibility nas seções inferiores. CLS perto de zero.
- Acessibilidade 100: contraste AA em todos os pontos em que texto colorido ficava sobre fundo claro, rótulos aria nos formulários, alvos de toque de no mínimo 24 px, modais fechando com Esc e uma variante prefers-reduced-motion nas animações.
- Os quatro pontos perdidos em Práticas recomendadas são o preço de pedir geolocalização no carregamento da página: o Lighthouse quer essa permissão atrás de um clique, mas aí o salto automático para a cidade certa deixa de existir. Uma troca consciente de pontos por funcionalidade, combinada com o cliente. Antes da geolocalização, o site marcava 100 nas quatro categorias.
Segurança e confiabilidade
- Os segredos ficam apenas em um config fora do repositório; nenhum token chega ao HTML ou ao JS.
- As pastas com conteúdo, PHP de serviço, fragmentos de template e scripts do desenvolvedor estão fechadas ao acesso direto, com uma proteção reserva caso o mod_rewrite esteja desligado.
- A saída é escapada em todo ponto em que dados entram na marcação; os dados estruturados são codificados de modo que um </script> perdido no texto não rasgue a página; a travessia de diretórios é bloqueada em todos os caminhos montados a partir de entradas.
- O painel: hash de senha, comparação protegida contra timing, atraso após tentativa falha, regeneração de sessão, token CSRF em todas as ações de escrita e um cookie restrito à área administrativa, com httponly e secure conforme o protocolo real.
- O webhook do CRM é protegido por um segredo na URL — os webhooks do amoCRM não têm assinatura.
- Degradação suave em toda parte: sem config, entra o template; sem minificado, entra o original; CRM fora do ar, o Telegram assume; nada funcionou, o pedido vai para o log.
Por que o projeto é interessante
- 80 versões de cidade sem uma única cópia dos arquivos — substituir as declinações pelo buffer de saída se mostrou mais simples e mais confiável do que funções de template.
- Integração com o CRM real de outra empresa, sem ambiente de teste: mapa de campos gerado por script a partir de um dump da conta, junção de duplicatas pelos últimos dez dígitos do telefone e um dry run local da montagem do pedido, sem uma única chamada externa.
- O pedido nunca se perde: CRM → Telegram → log em disco, com sucesso declarado se qualquer elo funcionar.
- 100/100/96/100 em uma página de serviço com um stack totalmente próprio, sem CDN, sem camada de cache e sem bundlers — em hospedagem compartilhada barata.
- Ciclo completo por uma única pessoa: migração de conteúdo da Tilda → arquitetura → back-end → front-end → painel → integração com o CRM → preparação de mais de 100 imagens → SEO → otimização → deploy no domínio de produção.