Ir para o conteúdo principal

Devlog · Fase 0 · 2/7

A stack de aubia.dev: Octane, FrankenPHP e Inertia SSR

Uma latência dividida por cinco a dez graças a Octane e FrankenPHP, um SSR Inertia que serve o HTML completo já no primeiro byte: a stack de aubia.dev, bloco por bloco.

Publicado em 20 julho 20265 min de leitura
  • laravel
  • octane
  • frankenphp
  • inertia
  • vite

aubia.dev serve seu HTML completo já no primeiro byte e responde com uma latência dividida por cinco a dez em relação a um PHP-FPM clássico. Para uma landing, isso não é luxo: uma vitrine tem um único trabalho não negociável, ser vista. Pelos visitantes, mas antes pelos motores de busca e pelos motores de resposta de IA. Abro para você a stack, com o raciocínio por trás de cada bloco.

O rendering do lado do servidor como invariante de SEO

Uma aplicação React clássica retorna uma casca HTML vazia e depois popula a página em JavaScript. Um navegador se acomoda a isso. Um crawler ou um motor de resposta, por sua vez, precisa encontrar o conteúdo diretamente no HTML da primeira resposta.

É por essa razão que me proibi o rendering apenas do lado do cliente nesta vitrine. O SSR comanda ali todo o resto. O Inertia v3 renderiza as páginas React do lado do servidor, e o HTML completo sai já no primeiro byte: título, subtítulo, seções, metadados de SEO, tudo está presente antes que uma única linha de JavaScript seja executada do lado do cliente. A hidratação assume em seguida o comando pela interatividade. Essa regra, o conteúdo precisa existir no HTML inicial, dita todo o resto do código frontend, até a proibição de exibir conteúdo por meio de um useEffect.

Octane e FrankenPHP: o servidor que não reinicia

Instalei o Octane logo no primeiro dia do repo, no fim de abril de 2026, antes mesmo de escrever a menor linha de conteúdo. É uma escolha de base, não uma otimização acrescentada depois. Em PHP tradicional, cada requisição reinicia a aplicação: carregamento do framework, resolução do container, depois resposta. O Laravel Octane muda esse modelo. A aplicação sobe uma vez, e depois workers persistentes servem milhares de requisições dentro do mesmo processo.

Faltava escolher o driver. Olhei o RoadRunner e o Swoole, as duas outras opções do Octane, antes de decidir pelo FrankenPHP: ele embarca o servidor Caddy nativamente, com HTTP/3, certificados automáticos e streaming sem camada adicional a operar, e seu roadmap é ativo no ecossistema Laravel.

// config/octane.php
'server' => env('OCTANE_SERVER', 'roadrunner'),
# .env : le driver actif sur aubia.dev
OCTANE_SERVER=frankenphp

Sobre essa base, o bootstrap do Laravel permanece em memória e a latência por requisição cai por um fator de cinco a dez em relação ao cold start do PHP-FPM. Um tempo de resposta do servidor baixo e estável conta para o score de performance e, portanto, para o ranqueamento. Mas esse modelo impõe uma disciplina que abordo mais abaixo.

Comparação animada: o php-fpm inicializa o framework a cada requisição e depois morre, enquanto o Octane inicializa uma vez e atende as requisições sucessivas em um worker persistente.

Inertia v3: React sem uma API separada

O ponto que torna essa stack agradável é o Inertia. Ele faz a ponte entre Laravel e React sem que eu precise construir e versionar uma API REST separada. Os controllers retornam páginas React com suas props, do mesmo modo que retornariam views Blade. O SSR se ativa na configuração, sem servidor Node distinto a manter em desenvolvimento.

// config/inertia.php
'ssr' => [
    'enabled' => true,
    'url' => 'http://127.0.0.1:13714',
],

Concretamente, Inertia::render() substitui as views, as rotas permanecem definidas do lado do Laravel, e o React só se preocupa com o rendering. Uma única fonte de verdade para o roteamento, sem lógica duplicada entre um backend e um frontend que se ignoram.

Vite 8, Rolldown e React 19

Do lado do build, aubia.dev usa o Vite 8 com Rolldown, o bundler escrito em Rust integrado nativamente. O React 19 é compilado com o React Compiler, que memoiza automaticamente os componentes sem anotações manuais. O particionamento do bundle é conduzido explicitamente para isolar as grandes dependências em seus próprios chunks:

// vite.config.ts
codeSplitting: {
    minSize: 20_000,
    groups: [
        { name: 'react', test: /node_modules\/(react|react-dom|scheduler)\//, priority: 50 },
        { name: 'motion', test: /node_modules\/(motion|motion-dom)\//, priority: 40 },
        { name: 'fontawesome', test: /node_modules\/@fortawesome\//, priority: 30 },
    ],
},

Separar o React, as animações e os ícones permite ao navegador colocar em cache o que muda pouco e recarregar apenas o que muda. O build de produção gera, além disso, um bundle SSR dedicado via vite build --ssr.

A restrição stateless

Um servidor que nunca reinicia mantém tudo em memória entre as requisições. É justamente isso que o torna rápido, e é também sua armadilha. Sob o Octane, armazenar um estado próprio de uma requisição em um singleton ou em uma propriedade estática o faria vazar para a requisição seguinte, servida pelo mesmo worker. Todo o código da vitrine respeita, portanto, esta regra: nenhum estado de requisição é retido para além do seu escopo.

A contagem da lista de espera, por exemplo, é carregada de forma diferida para não bloquear o rendering do servidor em uma chamada de rede, e seu resultado chega em uma segunda requisição do lado do cliente:

'waitlist' => [
    'count' => Inertia::defer(fn(): ?int => app(FetchWaitlistCountQuery::class)->run()),
],

O SSR permanece assim instantâneo, e o dado não determinístico nunca entra no primeiro render. Esse rigor stateless irriga cada módulo do repo.

O que a velocidade ainda não diz

Octane, FrankenPHP e Inertia dão uma vitrine rápida e indexável. Essa sobriedade vai ainda mais longe: a próxima etapa retira o próprio banco de dados, e com ele todo armazenamento local de dados pessoais.

Para ser avisado quando a beta 0.1 abrir, entre na lista de espera.

Esta construção é contada aqui, artigo após artigo. Embarque: o seu feedback desenhará o que vem a seguir.

Entrar na lista de espera