Ir para o conteúdo
Blog Papo Dev

De CMS Moderno a Plataforma Escalável: A Trajetória Real do MKT

15+ anos desenvolvendo em PHP. Dois projetos em produção (CIM Base, Omniaflow). Agora, um desafio pessoal. Esse artigo é um registro transparente da construção do MKT – Marketing & Code: as decisões técnicas deliberadas, os padrões que já validei em produção, e o caminho até se tornar uma plataforma real.

14 min de leitura
De CMS Moderno a Plataforma Escalável: A Trajetória Real do MKT

Começamos em janeiro de 2026 — um desafio que eu mesmo me fiz, aplicando tudo o que aprendi construindo CIM Base (site + CRM integrado) e Omniaflow (ERP multi-tenant com processamento financeiro real).

Não é um case de "tudo perfeito". É uma trajetória informada por produção.


Contexto: Dois Projetos em Produção Que Ensinaram Tudo

Antes de montar o MKTCode, eu tinha dois projetos reais em produção — e foi aprendendo com eles que entendi como construir do zero certo.

CIM Base — Site + CRM Integrado

Uma plataforma que une um site institucional robusto com um CRM de leads integrado, sem atrito entre os dois. Tudo em um monolito moderno com Inertia.js v2.

Lições aprendidas:

  • DDD tático não é over-engineering — é proteção contra o crescimento

  • RBAC granular é fundamental quando a equipe passa de 2 para 5 pessoas

  • UI library unificada (+50 componentes) reduz tempo de feature em 40%

  • Site + CRM integrado no mesmo monolito melhora conversão drasticamente (não há "exporta/importa dados" manual)

Omniaflow — ERP Multi-Tenant com Processamento Financeiro

Um sistema educacional que processa cobranças reais (PIX/Boletos), conciliação automática e isolamento total entre 40+ franquias.

Lições aprendidas:

  • Gateway Pattern salva você quando precisa trocar de provedor externo

  • Multitenancy por design (não por filtros de banco de dados) é impossível de quebrar

  • WebSockets em tempo real (Laravel Reverb) transformam a experiência do usuário

  • Valores em centavos (amount_cents) eliminam erros de ponto flutuante

  • Event-driven architecture escala sem overhead operacional


O Problema Que Começou Tudo

Como desenvolvedor sênior, sempre tive um pé em dois mundos:

Mundo 1: Construir ERPs e plataformas SaaS complexas para clientes. Aí aprendi o custo de decisões arquiteturais ruins.

Mundo 2: Nunca tinha um site próprio que refletisse realmente o que eu faço. Sites "prontos" me frustravam.

Então em janeiro de 2026, decidi:

Construir o site da MC - Marketing & Code — aplicando exatamente o que já sei funcionar em produção.

Não era questão de ego. Era questão de controle e de validação prática.

Eu queria:

  • Performance extrema (não sei por que CMS tradicionais são tão lentos)

  • Arquitetura que não envelhecesse em 2 anos

  • Fundação para evoluir de "site institucional" para "plataforma de marketing + CRM"

  • Aplicar padrões (DDD tático, Gateway Pattern, RBAC) que já comprovei em produção

Então decidi: construir do zero, e certo.


Por Que o MKTCode é Diferente (E Por Que Tem Que Ser)

O MKTCode não é um terceiro projeto repetindo padrões. É um desafio pessoal de aplicar tudo o que aprendi em um projeto que é meu.

Diferente de CIM Base ou Omniaflow (que são soluções para clientes), o MKTCode é:

  • O site da minha agência

  • O lugar onde publico minha trajetória

  • A plataforma onde valido novas ideias

  • O que eu recomendo quando alguém pergunta: "Como você estruturaria um projeto do zero?"

Por isso cada decisão é deliberada. Cada padrão já foi testado em produção.


A Stack: Escolhas Deliberadas (Não Aleatórias)

Essa parte é importante porque toda escolha tem uma razão.

Backend: Laravel 12 + PHP 8.4

Por que Laravel?

Existem outras opções (Symfony, API platforms), mas Laravel oferece algo raro: opinativo sem ser engessado.

  • Ecossistema maduro (Filament, Fortify, Spatie packages)

  • Convenções que fazem sentido (não preciso inventar a roda)

  • Performance decente (PHP 8.4 é realmente rápido agora)

  • Comunidade que entende DDD e Clean Code

// app/Providers/AppServiceProvider.php
class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        // Força HTTPS em produção automaticamente
        if (app()->isProduction()) {
            \URL::forceRootUrl(config('app.url'));
            \URL::forceScheme('https');
        }
    // Super admin têm acesso irrestrito
    Gate::before(function ($user, $ability) {
        return $user->hasRole('super_admin') ? true : null;
    });
    
    // Observers disparam automações
    Post::observe(PostObserver::class);
    Project::observe(ProjectObserver::class);
    User::observe(UserObserver::class);
}

}

Isso não parece muito, mas reflete uma decisão arquitetural: Observers como primeira classe para side effects, não como truques.

Admin: Filament v5

Inicialmente considerei Filament v4, mas v5 stable chegou:

  • Navegação mais rápida (menos renders desnecessários)

  • Ações contextuais integradas (WhatsApp, Email direto da tabela)

  • Componentes compostos (Grid, Section, Tabs) que não parecem gerados automaticamente

  • Table builders com Stack layout (dados empilhados, sem scroll horizontal)

// app/Providers/Filament/AdminPanelProvider.php
return $panel
->id('admin')
->login()
->multiFactorAuthentication([
AppAuthentication::make()->recoverable(),
EmailAuthentication::make(),
])
->passwordReset()
->emailVerification()
->path('admin')
->colors(['primary' => Color::Neutral])
->profile(EditProfile::class)
->discoverResources(in: app_path('Filament/Resources'))
->middleware([
EncryptCookies::class,
StartSession::class,
AuthenticateSession::class,
VerifyCsrfToken::class,
SubstituteBindings::class,
])
->authMiddleware([Authenticate::class]);

O importante: Filament é headless o suficiente pra coexistir com Inertia/Vue sem conflitos de rendering.

Frontend: Inertia.js v2 + Vue 3 + SSR

Esse é o diferencial real.

Inertia.js é a ponte perfeita entre Laravel e Vue:

  • Sem REST API boilerplate (Inertia cuida disso)

  • TypeScript completo (fewer bugs)

  • SSR habilitado (Google adora)

  • Deferred Props (lazy loading nativo)

// resources/js/composables/useSeo.ts
export function useSeo(props: SeoProps) {
const canonicalUrl = props.url
? props.url.startsWith('http')
? props.url
: ${SITE_URL}${props.url}
: undefined;

return {
    siteName: SITE_NAME,
    title: props.title,
    description: props.description || SITE_DESCRIPTION,
    image: props.image
        ? props.image.startsWith('http')
            ? props.image
            : `${SITE_URL}${props.image}`
        : `${SITE_URL}${SITE_OG_IMAGE}`,
    url: canonicalUrl,
    type: props.type || 'website',
    keywords: props.keywords || SITE_KEYWORDS,
    author: props.author || SITE_AUTHOR,
};

}

Um useSeo composable que abstrai toda a complexidade de metags, open graph, canonical URLs. Tudo tipado, reutilizável.

Styling: Tailwind CSS v4

Não há discussão aqui. Tailwind é produtividade pura.

Database: MySQL/MariaDB + Redis

  • MySQL/MariaDB: relacional, índices, transações. Padrão.

  • Redis: cache de queries frequentes, session store, fila de jobs.

Ambas em produção. Ambas essenciais para escala.


A Arquitetura Real

Aqui é onde a maioria dos artigos mente.

A documentação diz "Clean Architecture". Vou ser honesto: é o máximo possível sem virar overengineering.

Actions: Lógica Reutilizável

// app/Actions/Public/ProcessInquiry.php
declare(strict_types=1);

namespace App\Actions\Public;

use App\Models\Inquiry; use Illuminate\Support\Facades\Log;

class ProcessInquiry { /** * Process the inquiry from contact form. * * @param array<string, string> $data */ public function execute(array $data): void { Inquiry::create($data); Log::info('Inquiry processed:', $data); } }

Isso é pequeno, intencionalmente.

Por quê? Porque essa action será reutilizada por:

  • Controller Web

  • API endpoint

  • Job assíncrono

  • Webhook de terceiro (futuramente)

Se tivesse lógica de notificação, como a do Telegram aqui, estaria acoplada. Em vez disso, deixo os Observers cuidarem.

Observers: Automações Desacopladas

// app/Observers/InquiryObserver.php
class InquiryObserver
{
public function created(Inquiry $inquiry): void
{
// Dispatch async job, não executar síncrono
SendInquiryNotificationJob::dispatch($inquiry)
->onQueue('notifications');

    // Log
    Log::info(&#039;New inquiry created&#039;, [&#039;id&#039; =&gt; $inquiry-&gt;id]);
}

}

A decisão crítica aqui: observers não fazem work pesado. Eles dispatcham jobs.

Isso foi aprendizado do Omniaflow — se Telegram API demora 500ms e temos 100 inquiries/dia, a última espera 50 segundos. Não aceitável.

Services: Integrações Limpas

// app/Services/Telegram/TelegramNotifier.php
class TelegramNotifier
{
public function send(TelegramBotTarget $target, Notification $notification): void
{
// Lógica de envio para Telegram
// Retry logic, erro handling
}
}

// app/Services/Telegram/TelegramBotTarget.php class TelegramBotTarget { use Notifiable;

public function __construct(public readonly string $chatId) {}

public static function default(): self
{
    return new self(config(&#039;telegram.bot.chat_id&#039;));
}

}

Value Objects + Notifiable trait é um padrão elegante. Permite enviar notificações sem depender de models de banco de dados.


Decisões Críticas Que Fizeram Diferença

1. Segurança desde o Dia 1

// app/Providers/Filament/AdminPanelProvider.php
->multiFactorAuthentication([
AppAuthentication::make()->recoverable(),
EmailAuthentication::make(),
])

MFA não é "feature futura". É default. Desde o início.

// app/Providers/AppServiceProvider.php
Password::defaults(fn (): ?Password => app()->isProduction()
? Password::min(12)
->mixedCase()
->letters()
->numbers()
->symbols()
->uncompromised()
: null,
);

Senhas têm regras estritas em produção. Isso parece paranoia até você ver um hack.

2. Rate Limiting Em Camadas

// app/Providers/FortifyServiceProvider.php
RateLimiter::for('login', function (Request $request) {
$throttleKey = Str::transliterate(
Str::lower($request->input(Fortify::username())) . '|' . $request->ip()
);
return Limit::perMinute(5)->by($throttleKey);
});

5 tentativas por minuto por (username + IP). Brute force fisicamente impossível.

3. SSR Implementado Corretamente

Muitos projetos dizem "SSR", mas renderizam apenas metags. Aqui, renderiza HTML completo no servidor.

// resources/js/components/SeoHead.vue
<script setup lang="ts">
import { Head } from '@inertiajs/vue3';
import { useSeo } from '@/composables/useSeo';

const props = withDefaults(defineProps<SeoProps>(), { type: 'website', noIndex: false, image: SITE_OG_IMAGE, keywords: SITE_KEYWORDS, });

const seo = useSeo(props); </script>

<template> <Head> <title>{{ seo.title }}</title> <meta name="description" :content="seo.description" /> <meta name="robots" :content="seo.noIndex ? 'noindex, nofollow' : 'index, follow'" /> <link v-if="seo.url" rel="canonical" :href="seo.url" />

    &lt;!-- Open Graph --&gt;
    &lt;meta property=&quot;og:type&quot; :content=&quot;seo.type === &#039;profile&#039; ? &#039;profile&#039; : seo.type&quot; /&gt;
    &lt;meta property=&quot;og:title&quot; :content=&quot;seo.title&quot; /&gt;
    &lt;meta property=&quot;og:description&quot; :content=&quot;seo.description&quot; /&gt;
    &lt;meta property=&quot;og:image&quot; :content=&quot;seo.image&quot; /&gt;
    
    &lt;!-- Article only --&gt;
    &lt;template v-if=&quot;seo.type === &#039;article&#039; &amp;&amp; seo.publishedAt&quot;&gt;
        &lt;meta property=&quot;article:published_time&quot; :content=&quot;seo.publishedAt&quot; /&gt;
        &lt;meta property=&quot;article:author&quot; :content=&quot;seo.author&quot; /&gt;
    &lt;/template&gt;
&lt;/Head&gt;

</template>

Composable reutilizável, tipado, sem bugs de metags esquecidas.


O Que o MKT Já Funciona Hoje

CMS com Performance Real

  • Posts com SEO nativo

  • Projetos como portfolio

  • Categorias e tags polimórficas

  • MediaLibrary para uploads

  • Sitemap dinâmico

Performance (Core Web Vitals):

  • LCP: ~1.2s (SSR)

  • FID: ~0.1s

  • CLS: 0 (sem layout shift)

Não é acidente. É resultado de decisões deliberadas.

Lead Capture Que Funciona

  • Formulário de contato com validação

  • Persistência em Inquiry model

  • Notificações Telegram automáticas

  • Logging de tudo

Isso não parece muito, mas é a base de um motor de leads.

Admin Que Não Dá Raiva

Filament com:

  • Dashboards dinâmicos

  • RBAC via Spatie Permission + Filament Shield

  • Ações rápidas (WhatsApp, Email)

  • Indicadores visuais (badges por status)

Administrar o site é satisfatório, não tedioso. Isso importa.


Os Desafios (Sendo Honesto)

Performance em Escala

Problema Real: SSR em Node.js é bom até ~500 visitas/hora. Depois disso, a fila de renderização cresce.

Solução em Roadmap: PM2 com cluster mode (multi-processo) e cache agressivo via Redis.

Queries Pesadas

MediaLibrary, Tags polimórficas, relacionamentos — tudo isso pode vira N+1 disaster se não otimizado.

Solução Atual: Índices bem planejados, eager loading com with(), e cache estratégico.

Solução Futura: Redis cache para queries frequentes de read (posts, projetos).

Filament v5 vs Inertia SSR

Ambos em produção, mas não documentei bem como sessão é compartilhada entre pipelines.

Atualmente:

  • Logout em /admin → destrói session → Inertia redireciona pra /login

  • Isso funciona, mas foi por acaso, não por design

Preciso documentar e testar mais cenários edge.


Decisões Futuras (O Roadmap Real)

Aqui é onde o MKTCode não é apenas um site — é uma evolução natural inspirada pelo que já comprovei em produção.

Curto Prazo

1. Cache Strategy com Redis

Aprendizado do Omniaflow: queries frequentes não escalam sem cache inteligente.

// Futuro
Route::get('/blog', function() {
$posts = Cache::remember('blog.posts.page.1', 3600, fn() =>
Post::with('category', 'tags')
->published()
->latest()
->paginate(10)
);

return Inertia::render(&#039;Blog/Index&#039;, [
    &#039;posts&#039; =&gt; PostResource::collection($posts)
]);

});

// Invalidação smart quando post criado class PostObserver { public function created(Post $post): void { Cache::tags('blog')->flush(); } }

2. Security Headers Middleware

CSP, X-Frame-Options, HSTS — padrão já implementado em Omniaflow.

3. Structured Data (Schema.org)

JSON-LD para articles, organization, breadcrumbs. Google (e seus clientes) precisam disso.

Médio Prazo: Inquiry → Lead + CRM Leve

Esse é o ponto de inflexão que validei em CIM Base.

Quando você integra site + CRM no mesmo monolito, a conversão de leads melhora drasticamente porque não há atrito entre os sistemas.

Inquiry é uma mensagem. Lead é uma oportunidade.

// app/Models/Lead.php (novo)
class Lead extends Model
{
public $status; // 'novo', 'contatado', 'proposta', 'fechado'
public $source; // 'contato', 'ebook', 'landing-page'
public $potential_value; // Preenchido manualmente
public $assignedTo; // Responsável
}

// Pipeline de vendas const STATUSES = ['novo', 'contatado', 'proposta', 'fechado'];

Isso abre porta para:

  • CRM leve integrado — como em CIM Base

  • Pipeline visual no Filament — drag-drop de leads entre estágios

  • Automações baseadas em status — notificações, webhooks, integrações

  • RBAC granular — cada vendedor vê apenas seus leads

4. Formulários Dinâmicos (Multi-Landing Page)

Aprendizado do Omniaflow: cada ponto de contato diferente captura dados diferentes.

// app/Models/Form.php (novo)
class Form extends Model
{
public $fields; // JSON: [{ name: 'name', type: 'text' }, ...]
public $redirectUrl; // URL pós-submit
public $successMessage; // Mensagem customizada
}

// Cada form gera leads segmentados // /ebook-marketing?form=ebook-lead // /consultoria-gratis?form=consultoria // /proposta-customizada?form=proposta

Longo Prazo: Automações Event-Driven

Padrão que validei em Omniaflow: quando o sistema é event-driven, escala sem overhead operacional.

// Futuro
event(new LeadCreated($lead));

// Listeners automáticos: // - SendWelcomeEmail // - CreatePipelineCard // - TriggerAutomation // - LogActivity (Spatie Activity Log) // - WebhookDispatch

Super Longo Prazo: SaaS Evolution

Com infraestrutura de leads + automações, viabiliza o que Omniaflow já comprovou:

  • Multi-tenant — agências usando MKTCode pra gerenciar leads de seus clientes

  • Cobrança via Gateways de pagamento (Stripe, Asaas, etc)

  • API pública — webhooks, integração com terceiros

  • Isolamento por design — não por filtros (garantia arquitetônica)

Isso não é fantasia. É o próximo passo natural, aplicando tudo o que Omniaflow já comprovou em produção com clientes reais.


Por Que Documentar Isso?

15+ anos desenvolvendo, e a lição mais importante é:

Código é passageiro. Decisões são permanentes.

Um controller ruim você refatora em um dia. Uma decisão arquitetural ruim você lida por anos.

Por isso documentei:

  • Estou usando Inertia SSR porque performance importa pra SEO

  • Estou usando Observers porque side effects precisam ser desacoplados

  • Estou usando Actions porque reutilização é obrigatória

  • Estou usando Redis porque cache é não-negociável em escala

Não é "best practices genérico". É decisão deliberada com razão, validada em dois projetos em produção.


Conclusão: Tecnologia Que Resolve

O MKTCode começou como um desafio pessoal. Evoluiu para um CMS. Vai virar uma plataforma.

Mas em todo passo:

Tecnologia em serviço do problema, não ao contrário.

Escolhemos Laravel não porque é "popular". Escolhemos porque oferece flexibilidade sem perder opinion.

Escolhemos Inertia não porque "SPA é hype". Escolhemos porque SSR é melhor para negócio.

Escolhemos Filament não porque "low-code é future". Escolhemos porque permite iterar rápido no admin sem sacrificar controle.

Escolhemos aplicar padrões (DDD, RBAC, Gateway Pattern) porque já os vimos funcionar em produção, não em blog posts.


Esse é o começo de uma trajetória.

Os próximos passos vão transformar o MKTCode de "site bonito com formulário" em "plataforma real de marketing + CRM".

E todas as decisões que fizemos até aqui foram pra garantir que quando chegar lá, o código não envelheça.

#Laravel 12 #Inertia.js #CMS Moderno #Filament
Compartilhar:
Flavio Moreira

Escrito por

Flavio Moreira

Desenvolvedor e fundador da mktcode. Apaixonado por transformar ideias em produtos digitais de alta performance.