Vai al contenuto principale

Diario di bordo · Fase 0 · 2/7

Lo stack di aubia.dev: Octane, FrankenPHP e Inertia SSR

Una latenza divisa per cinque o dieci grazie a Octane e FrankenPHP, un SSR Inertia che serve l'HTML completo fin dal primo byte: lo stack di aubia.dev, blocco per blocco.

Pubblicato il 20 luglio 20265 min di lettura
  • laravel
  • octane
  • frankenphp
  • inertia
  • vite

aubia.dev serve il suo HTML completo fin dal primo byte e risponde con una latenza divisa per cinque o dieci rispetto a un PHP-FPM classico. Per una landing non è un lusso: un sito vetrina ha un solo compito non negoziabile, essere visto. Dai visitatori, ma prima di tutto dai motori di ricerca e dai motori di risposta IA. Le apro lo stack, con il ragionamento dietro ogni blocco.

Il rendering lato server come invariante SEO

Un'app React classica restituisce un guscio HTML vuoto, poi popola la pagina in JavaScript. Un browser se la cava. Un crawler o un motore di risposta, invece, deve trovare il contenuto direttamente nell'HTML della prima risposta.

È per questo che mi sono vietato il rendering solo lato client su questo sito. L'SSR detta tutto il resto. Inertia v3 renderizza le pagine React sul server, e l'HTML completo parte dal primo byte: titolo, sottotitolo, sezioni, metadati SEO, tutto è presente prima che una sola riga di JavaScript giri sul client. L'idratazione subentra poi per l'interattività. Questa regola, il contenuto deve esistere nell'HTML iniziale, detta tutto il resto del codice frontend, fino al divieto di renderizzare contenuto tramite uno useEffect.

Octane e FrankenPHP: il server che non riparte mai

Ho installato Octane il primo giorno del repo, a fine aprile 2026, prima ancora di scrivere una sola riga di contenuto. È una scelta di fondazione, non un'ottimizzazione aggiunta dopo. Nel PHP tradizionale, ogni richiesta riavvia l'applicazione: caricamento del framework, risoluzione del container, poi risposta. Laravel Octane cambia questo modello. L'applicazione si avvia una volta, poi dei worker persistenti servono migliaia di richieste all'interno dello stesso processo.

Restava da scegliere il driver. Ho valutato RoadRunner e Swoole, le altre due opzioni di Octane, prima di optare per FrankenPHP: integra nativamente il server Caddy, con HTTP/3, certificati automatici e streaming senza alcun livello aggiuntivo da gestire, e la sua roadmap è attiva nell'ecosistema Laravel.

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

Su questa base, il bootstrap di Laravel resta in memoria e la latenza per richiesta cala di un fattore da cinque a dieci rispetto all'avvio a freddo di PHP-FPM. Un tempo di risposta del server basso e stabile conta per il punteggio di performance, e quindi per il posizionamento. Ma questo modello impone una disciplina di cui parlo più avanti.

Confronto animato: php-fpm avvia il framework a ogni richiesta e poi muore, mentre Octane si avvia una volta e serve le richieste successive da un worker persistente.

Inertia v3: React senza un'API separata

Il punto che rende gradevole questo stack è Inertia. Fa da ponte tra Laravel e React senza che io debba costruire e versionare un'API REST separata. I controller restituiscono pagine React con le loro props, allo stesso modo in cui restituirebbero delle viste Blade. L'SSR si attiva nella configurazione, senza un server Node separato da mantenere in sviluppo.

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

In concreto, Inertia::render() sostituisce le viste, le route restano definite lato Laravel, e React si preoccupa solo del rendering. Un'unica fonte di verità per il routing, nessuna logica duplicata tra un backend e un frontend che si ignorano.

Vite 8, Rolldown e React 19

Sul versante build, aubia.dev usa Vite 8 con Rolldown, il bundler scritto in Rust integrato nativamente. React 19 è compilato con il React Compiler, che memoizza automaticamente i componenti senza annotazioni manuali. Lo splitting del bundle è guidato in modo esplicito per isolare le dipendenze pesanti in chunk dedicati:

// 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 },
    ],
},

Separare React, le animazioni e le icone permette al browser di mettere in cache ciò che cambia di rado e di ricaricare solo ciò che cambia. La build di produzione genera inoltre un bundle SSR dedicato tramite vite build --ssr.

Il vincolo stateless

Un server che non riparte mai tiene tutto in memoria tra una richiesta e l'altra. È esattamente ciò che lo rende veloce, ed è anche la sua trappola. Sotto Octane, memorizzare uno stato specifico di una richiesta in un singleton o in una proprietà statica lo farebbe trapelare nella richiesta successiva, servita dallo stesso worker. Perciò tutto il codice del sito rispetta questa regola: nessuno stato di richiesta viene trattenuto oltre il suo scope.

Il contatore della lista d'attesa, per esempio, è caricato in modo lazy affinché il render lato server non si blocchi su una chiamata di rete, e il suo risultato arriva in una seconda richiesta lato client:

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

L'SSR resta così istantaneo, e il dato non deterministico non entra mai nel primo render. Questo rigore stateless attraversa ogni modulo del repo.

Ciò che la velocità non dice ancora

Octane, FrankenPHP e Inertia danno un sito veloce e indicizzabile. Questa sobrietà va ancora oltre: la tappa successiva toglie il database stesso, e con esso ogni memorizzazione locale di dati personali.

Per essere avvisato all'apertura della beta 0.1, si iscriva alla lista d'attesa.

Questo cantiere si racconta qui, articolo dopo articolo. Salite a bordo: i vostri riscontri disegneranno il seguito.

Iscriviti alla lista d'attesa