Lojas virtuais

Lojas virtuais feitas à mão em stack puro — sem CMS, sem frameworks e sem infraestrutura pesada. Ciclo completo: catálogo, carrinho, pagamento online, painel administrativo, SEO e analytics.

Se você não é desenvolvedor

Uma loja virtual é mais do que uma vitrine de produtos. É o estoque que precisa bater com o depósito real, o pagamento que precisa chegar na sua conta e um painel onde você mesmo muda preços sem chamar ninguém. Tudo isso aqui é escrito à mão, não montado com blocos prontos.

Na prática isso significa: a loja não depende do serviço de terceiros nem do plano mensal dele, roda rápido em hospedagem barata e não quebra quando um plugin de fora é atualizado. A desvantagem honesta — quem faz alterações é o desenvolvedor, não você pelo painel de um construtor.

Loja virtual

Vector Pola — loja de pisos

FunçãoDesenvolvedor único — full-stack: arquitetura de dados, back-end, front-end, painel administrativo, SEO e deploy
StackPHP 8 · SQLite (PDO) · HTML · CSS · JavaScript — sem CMS, frameworks, npm nem Composer

Loja virtual de pisos em operação — laminado, SPC e vinílico rígido, tábua de engenharia e tábua de parquê — para um varejista com dois showrooms, em Moscou e Krasnogorsk. O catálogo tem cerca de 7 600 produtos ativos: 3 300 itens de laminado, 3 900 de SPC e vinílico rígido, além de tábua de engenharia e de parquê. A vitrine é escrita em PHP puro, sem CMS nem frameworks, roda em hospedagem compartilhada barata e não exige nenhuma etapa de build. A decisão de engenharia central é um armazenamento em duas camadas: um JSON canônico de 17,5 MB e um cache SQLite derivado, de onde a vitrine realmente lê. Os pedidos chegam como solicitações no Telegram, e não como pagamento online — é assim que funciona o processo de vendas do cliente.

O que era precisoO cliente tem duas lojas físicas e um catálogo de 7.600 itens. Era preciso colocá-lo online sem que o site caísse sob o próprio peso nem exigisse um servidor caro.

O que ficou prontoA loja roda em hospedagem comum e barata, os produtos são atualizados por arquivo em vez de na mão, e não há mensalidade de plataforma.

Arquitetura de dados

A ideia central: products.json é a fonte da verdade e catalog.sqlite é um read-cache derivado. A vitrine não decodifica mais 17,5 MB de JSON a cada requisição — ela consulta um banco pronto via PDO.

  • O painel escreve no JSON e o banco é reconstruído uma única vez por requisição, via register_shutdown_function — essencial para o importador, que salva produtos em laço e, de outro modo, dispararia uma reconstrução por linha.
  • A reconstrução é atômica: escreve em catalog.sqlite.tmp e troca o arquivo com rename. Em caso de erro, o temporário é removido e o banco antigo continua vivo — nenhuma requisição pega um banco escrito pela metade.
  • Duas tabelas: products (22 colunas, flags active/in_stock/popular/promo e a coluna search_text = lower(name + brand) para busca sem distinção de caixa em cirílico) e product_facets, com os valores de filtro expandidos por produto. Cinco índices em produtos, três em facetas.
  • Resistência à dessincronização de schema: as consultas às colunas novas ficam em try/catch. Se o PHP subir antes da reconstrução do banco, nem a vitrine nem o painel caem em 500 — degradam de forma suave, com bloco vazio ou fallback para o JSON.

Catálogo e filtros

Cerca de 7 600 produtos ativos em 4 das 9 categorias; as demais mostram o estado “seção em preenchimento” em vez de uma página vazia.

  • Filtros facetados configurados a partir das especificações reais de cada categoria: marca, classe de abrasão, espessura, tipo de instalação, espécie de madeira e acabamento. A lógica é OU dentro da faceta e E entre facetas, com contador de produtos em cada valor.
  • Faixa de preço que sugere os limites reais da categoria, filtro “somente em estoque”, ordenação por preço e paginação no servidor de 24 produtos por página.
  • Os filtros têm auto-submit: no desktop, na hora do clique; na gaveta mobile, pelo botão “Mostrar”; no preço, pelo Enter.
  • Busca por nome e marca com consultas de múltiplas palavras: cada palavra precisa aparecer no search_text normalizado.
  • Os dados do catálogo estão normalizados: sem preços zerados, sem slugs duplicados, marcas preenchidas e chaves de especificação canonizadas.

Página do produto e pedido

O piso tem uma peculiaridade própria: vende-se por pacote, mas o comprador pensa em metros quadrados.

  • Calculadora de área: você informa os metros quadrados e ela calcula a quantidade de pacotes (arredondando para cima), a área efetiva e o total final. O primeiro cálculo é renderizado no servidor, então a página continua fazendo sentido sem JavaScript.
  • Galeria com troca de fotos, especificações em tabela, botões “Ao carrinho” e “Comprar em um clique”, links de compartilhamento no WhatsApp e Telegram e cópia do endereço.
  • Carrinho no cliente (localStorage) que recalcula os pacotes de cada linha; o checkout envia uma solicitação, não um pagamento — a composição do pedido e o total vão para o Telegram em texto.
  • Placeholder de imagem em duas camadas: images vazio cai no fallback do servidor, link quebrado cai no onerror do cliente.

Solicitações e notificações

  • Todos os formulários — consultoria, compra em um clique, carrinho, entrega e parceria com designers — passam por um único handler até um bot do Telegram, marcados com a origem da solicitação.
  • Timeouts separados para a conexão e para a operação inteira (5 s e 15 s). Se a API falhar, a solicitação é registrada no arquivo fechado data/leads-failed.log — o lead sobrevive mesmo com o Telegram fora do ar.
  • A assinatura de promoções é gravada em JSON sob flock, com deduplicação por e-mail: reenviar atualiza nome, telefone e data em vez de criar outra linha.
  • No cliente, uma única inicialização de formulários lê o endpoint e a mensagem de sucesso de data-attributes; a máscara de telefone e a validação são reaproveitadas por todos os formulários.

Painel administrativo próprio

Totalmente feito à mão, atrás de login com sessões PHP.

  • Catálogo: CRUD de produtos, busca e paginação no servidor e abas “Lista”, “Populares” e “Em promoção” — as marcações no produto alimentam os carrosséis da home, e uma seção vazia simplesmente não é renderizada.
  • Importação e exportação XLSX com leitor e escritor próprios, sobre ZipArchive e XML manual — sem Composer e sem bibliotecas externas. A exportação transforma cada chave de especificação em uma coluna, o modelo vem com uma linha de exemplo e as colunas fixas e adicionais têm cores diferentes.
  • A importação faz upsert: primeiro por slug, depois por SKU; corrige SKUs do tipo “1.0” que o Excel e o Google Sheets geram a partir de inteiros e consegue baixar fotos por URL.
  • Upload de imagens: crop centralizado para um quadrado de 800×800 e conversão para WebP no servidor (GD), aceitando arquivo e URL, com verificação de MIME.
  • Assinaturas: lista com os mais recentes no topo, exclusão por POST com confirmação e redirect PRG, e exportação em CSV (com BOM para o Excel) e XLSX.
  • Proteção do login: captcha aritmético, bloqueio de 5 minutos após 5 tentativas erradas e recuperação de senha por Telegram e e-mail. A reconstrução manual do banco fica em uma página própria.

SEO

  • sitemap.xml dinâmico direto do SQLite: páginas estáticas, apenas as categorias não vazias e todos os produtos ativos com lastmod vindo da data de atualização — hoje, cerca de 7 600 URLs.
  • Schema.org / JSON-LD: Product + Offer com priceValidUntil, itemCondition e disponibilidade dinâmica nas páginas de produto, e BreadcrumbList nos produtos, nas categorias e no índice do catálogo.
  • Controle consciente da indexação: a paginação limpa é indexável, com self-canonical e rel prev/next, enquanto as combinações de filtros e ordenação vão para noindex,follow, para não gerar duplicatas.
  • Title e description de SEO são definidos por produto no painel, com fallback para um modelo montado a partir do nome. URLs legíveis /catalog/{categoria}/{slug}/ via mod_rewrite, com transliteração do cirílico para o slug e unicidade garantida.
  • A busca interna e o carrinho estão fechados para indexação, e o robots.txt e o sitemap estão de acordo entre si.

Performance e hospedagem

A velocidade vem da arquitetura de armazenamento, e não da infraestrutura: sem CDN, sem camada de cache, sem daemons em segundo plano — hospedagem compartilhada barata na Beget e deploy por rsync.

  • Abandonar o parse de 17,5 MB de JSON em cada requisição em favor de consultas SQL indexadas é o maior ganho no tempo de resposta.
  • Todas as ~7 800 fotos de produto foram padronizadas em WebP 800×800 e os logotipos das marcas em WebP 320×180. O pipeline de preparação são scripts locais em Python/PIL que cortam as bordas brancas e limitam o upscale.
  • Preload e fetchpriority na imagem do hero (LCP), lazy-load nas demais e width/height explícitos — zero deslocamento de layout.
  • Gzip e cabeçalhos de cache para estáticos no .htaccess, com cache-busting dos assets por ?v=N.
  • Analytics: Yandex.Metrica com webvisor e clickmap.

Segurança e robustez

  • A pasta /data/, com o JSON dos produtos e a base de inscritos, está fechada tanto no .htaccess (uma RewriteRule com [F]) quanto no robots.txt; o config.php com o token do bot tem o acesso direto negado.
  • Em /uploads/ a execução de PHP está desativada e só imagens são servidas; os arquivos internos do painel são bloqueados por FilesMatch e cada um ainda verifica se não foi chamado diretamente.
  • 404 com sentido para produtos e categorias inexistentes: a checagem cobre não só o slug, mas também se a categoria da URL corresponde à categoria do próprio produto.

Por que o projeto é interessante

  • Um catálogo de 7 600 produtos rodando em hospedagem compartilhada, sem CMS, sem MySQL, sem npm e sem Composer — graças a um armazenamento bem pensado, e não a capacidade de máquina.
  • JSON como fonte da verdade e SQLite como read-cache derivado: escritas raras, leituras frequentes — um trade-off que encaixou melhor neste projeto do que um SGBD completo.
  • Os detalhes de engenharia que só aparecem em produção: reconstrução atômica do banco, uma reconstrução por requisição em vez de milhares durante uma importação, degradação suave na dessincronização de schema e log de fallback para solicitações perdidas.
  • Leitor e escritor de XLSX próprios em vez de uma biblioteca pesada — o cliente mantém o mix de produtos no Excel, e isso funciona sem nenhuma dependência externa.
  • Ciclo completo por uma única pessoa: arquitetura de dados → back-end → front-end → painel → pipeline de imagens → SEO → deploy.
Loja virtual

Wergrauf — loja de hidráulica

FunçãoDesenvolvedor único — full-stack: arquitetura, back-end, front-end, integrações, SEO e deploy
StackPHP · HTML · CSS · JavaScript — sem frameworks, CMS nem banco de dados

O cliente mudou de marca e quis elevar o nível da loja. Para a nova marca, montei do zero uma loja de materiais hidráulicos em stack puro — sem CMS, sem frameworks e sem MySQL. Os dados dos produtos ficam em arquivos JSON que sincronizam automaticamente com o Google Sheets. Ciclo completo: catálogo com 9 categorias, páginas de produto, carrinho, checkout e pagamento online, avaliações com moderação, painel administrativo próprio, analytics de e-commerce e SEO. O site mantém 90–100 pontos no PageSpeed Insights no desktop e no mobile.

O que era precisoO cliente mudou de marca e queria um site melhor que o anterior — com pagamento no próprio site e a possibilidade de gerir produtos sem chamar um programador.

O que ficou prontoOs produtos ficam em Planilhas Google, já familiares, e sobem sozinhos para o site. Pagamento, avaliações moderadas e painel administrativo funcionam sem mensalidade de plataforma.

Arquitetura

Uma arquitetura deliberadamente leve e “sem banco”: em vez de CMS e SQL, armazenamento em arquivos e uma integração com o Google Sheets funcionando como painel de conteúdo.

  • A fonte de dados é a API do Google Sheets: uma planilha, uma aba por categoria. Um script PHP de sincronização puxa os dados pela API e salva em JSON — o responsável pelo conteúdo mantém o mix em uma planilha familiar e o site atualiza com um clique.
  • Localização das imagens: durante a sincronização, as fotos externas dos produtos são baixadas para o servidor, convertidas em WebP e renomeadas pelo slug. Idempotente, com fallback em caso de falha.
  • Uma camada de overrides: edições manuais feitas no painel (meta tags, ocultar produtos, ordenação) são aplicadas sobre os dados da planilha — a planilha continua sendo a fonte da verdade, mas qualquer campo pode ser sobrescrito localmente.
  • Renderização do catálogo no servidor para linkagem interna correta e SEO; o JS no cliente cuida apenas dos filtros e da ordenação.

Principais funcionalidades

Catálogo e produtos: 9 categorias, um único template no servidor para a página de produto e para o catálogo; páginas de produto com galeria, troca de fotos, especificações e blocos “produtos similares” e “desta coleção”; filtragem no cliente por preço (slider duplo), modelo, cor e coleção, além de ordenação.

Carrinho, pedido, pagamento: carrinho no cliente (localStorage), checkout e compra em um clique, pagamento online via Ozon SBP, página de status do pedido com polling do pagamento e notificações de novos pedidos no Telegram por bot.

Avaliações: formulário com upload de até 4 fotos e três camadas de proteção antispam (honeypot + verificação do tempo de preenchimento + captcha aritmético), armazenamento em JSON e moderação pelo painel.

Assinatura de novidades que guarda a base de inscritos e a exibe no painel.

Painel administrativo próprio

Totalmente feito à mão, com autenticação por sessões PHP.

  • Dashboard com estatísticas por categoria.
  • Sincronização manual com o Google Sheets em um clique, com log dos resultados.
  • Visualização e edição dos produtos de cada categoria, incluindo meta tags (title/description) para SEO.
  • Adição de produtos manuais sobre os sincronizados e edição do conteúdo da página inicial.
  • Moderação de avaliações, lista de inscritos e log de sincronizações.
  • Ferramentas de serviço: otimização de imagens em lote e limpeza de lixo do sistema.

Performance (PageSpeed Insights)

O site foi levado a 90–100 pontos de Performance no desktop e no mobile.

  • Tamanhos de imagem responsivos: para cada foto de produto a sincronização gera tamanhos derivados (página de produto ~600px, miniatura ~160px) além do original usado na galeria — as imagens de produto ficaram 65–70% mais leves e as miniaturas sob a foto principal caíram de ~65 KB para ~2 KB.
  • WebP em tudo — produtos e logotipos (via <picture> com fallback).
  • Cumulative Layout Shift eliminado: width/height explícitos em todas as imagens.
  • Priorização de carregamento: fetchpriority na imagem principal (LCP), lazy-load no restante, preconnect ao domínio de analytics e defer nos scripts.
  • Contraste, títulos e fontes ajustados para o mobile.

SEO

  • Schema.org / JSON-LD: Product + Offer (disponibilidade dinâmica e priceValidUntil), BreadcrumbList nas páginas de produto, Organization + WebSite na home.
  • URLs canônicas, títulos keyword-first, hierarquia de headings correta, landmarks <main> e marcação semântica.
  • sitemap.xml e robots.txt gerados automaticamente.
  • Um feed YML para o Yandex.Direct (campanhas de produto, smart banners) que se atualiza sozinho após cada sincronização do catálogo.
  • Acessibilidade: rótulos ARIA, contraste conforme WCAG e navegação acessível.

Analytics

Analytics de e-commerce completo no Yandex.Metrica: metas e dataLayer nas ações-chave (adicionar ao carrinho, pedido, compra em um clique, pagamento aprovado, assinatura), proteção contra disparo duplicado de metas, webvisor e clickmap. As compras são rastreadas com proteção contra duplicidade durante o polling do pagamento.

Por que o projeto é interessante

  • Dependência zero de infraestrutura pesada: sem CMS, sem SQL, sem framework — e ainda assim uma loja completa, com pagamento, analytics e painel.
  • Google Sheets como back-end de conteúdo — uma integração pouco convencional, mas confortável para o cliente.
  • Performance como prioridade consciente: a otimização de imagens está embutida no pipeline de dados, não foi acoplada depois.
  • Ciclo completo por uma única pessoa: arquitetura, back-end, front-end, integrações, SEO, deploy e manutenção.
Loja virtual

Hizberg — loja de hidráulica

FunçãoDesenvolvedor único — front-end a partir do mockup do cliente, back-end, pagamentos, deploy
StackPHP · HTML · CSS · JavaScript (jQuery) — sem frameworks, CMS nem banco de dados

Um projeto comercial inicial: uma loja de materiais hidráulicos feita do zero a partir do mockup do cliente. Uma solução deliberadamente simples e “sem banco de dados” — o conteúdo é fixo nas páginas, produtos e preços ficam em arquivos PHP e os pedidos são gravados em uma estrutura de arquivos, sem SGBD. Ainda assim, a loja cobre o ciclo completo de venda: catálogo, páginas de produto com escolha de cor e quantidade, carrinho, checkout, pagamento online, rastreio do status do pedido, importação de avaliações de marketplaces e notificações ao cliente. Um MVP pragmático que, na época, era suficiente para o negócio vender de verdade.

O que era precisoO pedido era uma loja de materiais hidráulicos a partir de um layout pronto — com pagamento online e acompanhamento do pedido, e com o mínimo de manutenção.

O que ficou prontoTodo o ciclo de compra funciona sem nenhum banco de dados: não há o que quebrar e a hospedagem mais barata resolve. É um trabalho antigo, e o código mostra isso — fica aqui com honestidade.

Arquitetura

Uma estrutura de arquivos estática, sem SGBD e sem template engine: cada produto é uma pasta própria com seu index.php e suas imagens, os preços ficam em variáveis dentro de um único arquivo PHP compartilhado, as categorias (misturadores, sistemas de chuveiro, peças, acessórios) formam um catálogo de pastas aninhadas e os blocos comuns (cabeçalho, menu, rodapé) entram por includes. Uma abordagem propositalmente rústica, mas transparente e confiável para um mix de produtos estático.

Catálogo e produtos

Página do produto: galeria com zoom, escolha de cor alternando entre variantes, contador de quantidade que recalcula o preço na hora e breadcrumbs com marcação Schema.org. O carrinho é no cliente (localStorage), com cupons de desconto e frete grátis acima de um valor mínimo.

Pedidos e pagamento

Os pedidos ficam em uma estrutura de arquivos: cada um ganha uma pasta com seu status, os dados do cliente e uma página de pedido gerada. Pagamento online por cartão e via SBP pelo adquirente Tinkoff (sessão de pagamento pela API, redirecionamento para o checkout, troca automática de status ao confirmar o pagamento); como alternativa, por dados bancários (QR + PDF) ou dinheiro. O cliente recebe um código de rastreio e uma página de acompanhamento do status.

Painel administrativo

O mais simples possível: um único log de todos os pedidos, em que cada linha traz links de ação — “pago / concluído / fechado”. O gerente trata os pedidos direto pelo log: confirma o pagamento, muda o status, encerra. Exatamente a quantidade de funções que o fluxo exige, sem SGBD e sem interface pesada.

Avaliações

Importação de avaliações de marketplaces (Ozon, Wildberries): um parser lê páginas salvas, seleciona as avaliações com nota alta, limpa e remove duplicatas e as reúne em uma lista única que carrega mais conforme a rolagem. Os links levam de volta aos anúncios originais nos marketplaces.

Notificações

Ao finalizar o pedido, o cliente recebe um e-mail e um SMS com o número do pedido e o código de rastreio.

SEO

Title/description únicos em cada página, URLs legíveis seguindo a estrutura do catálogo, breadcrumbs com dados estruturados, sitemap.xml, robots.txt e favicon — um mínimo técnico básico, mas pensado.

Por que o projeto é interessante

Uma construção inicial “do zero, a partir do mockup, sem CMS nem frameworks”, feita à mão em cerca de uma semana (ainda antes da era das ferramentas de IA) — e mesmo assim uma loja funcional, com pagamento online real e processamento de pedidos. O mesmo cliente depois fez o rebranding para Wergrauf, já sobre uma arquitetura data-driven madura: os dois projetos juntos mostram a evolução da abordagem.

O que vem depois

Se o seu caso é parecido, veja quanto custa fazer um site — lá está o que forma o preço de uma loja e o que muda esse valor. Se você precisa de um site institucional em vez de uma loja, é a seção ao lado.