Devlog · Fase 0 · 2/7
Octane, FrankenPHP e Inertia SSR en aubia.dev
Octane, FrankenPHP e Inertia SSR sirven aubia.dev. Su funcionamiento, los límites del renderizado en el servidor y las reservas sobre las cifras de latencia publicadas.
- laravel
- octane
- frankenphp
- inertia
- vite

En aubia.dev, Laravel permanece cargado en memoria entre peticiones y React renderiza el contenido de las páginas en el servidor. El navegador recibe ese HTML y después JavaScript añade la interactividad.
Elegí Laravel Octane con FrankenPHP para ejecutar la aplicación y el SSR de Inertia para renderizar las páginas React. Quiero que los buscadores puedan leer el contenido sin tener que ejecutar el JavaScript del sitio.
Las cifras de latencia publicadas el 25 de agosto de 2026 indican dos resultados: entre 75 y 280 ms en caliente y 133 peticiones por debajo de 615 ms tras un periodo sin tráfico. Sin registros públicos ni un protocolo de medición que permitan verificarlas, no demuestran una mejora frente a PHP-FPM ni un tiempo de reactivación.
El modo worker de Octane
Con PHP-FPM, Laravel se inicializa en cada petición. Los procesos PHP pueden atender varias peticiones antes de reciclarse, según su configuración, incluido pm.max_requests. El arranque de la aplicación se repite sin necesitar un proceso nuevo cada vez.
El diagrama muestra lo que PHP-FPM repite en cada petición.
Cada petición siguiente vuelve a pasar por esa inicialización.
Laravel Octane arranca Laravel una vez por worker. Ese worker atiende las peticiones siguientes con la aplicación ya cargada. Los workers se reciclan o reinician de vez en cuando, por ejemplo, durante un despliegue.
En aubia.dev, Octane utiliza FrankenPHP, un servidor de aplicaciones PHP escrito en Go que integra el servidor web Caddy.
La siguiente variable de entorno selecciona este driver en una instalación ya configurada:
# .env: el driver activo en aubia.dev
OCTANE_SERVER=frankenphp
Con Octane, las peticiones pasan por un solo bloque de inicialización.
El bloque no reaparece entre dos peticiones: la aplicación cargada atiende las siguientes.
Mantener la aplicación en memoria exige vigilar qué almacena en ella. Los datos de un visitante, guardados en un singleton o una propiedad estática, pueden reutilizarse para el siguiente.
Octane restablece el estado del framework entre peticiones. No borra automáticamente todas las variables globales y propiedades estáticas del código de la aplicación. Los servicios que conservan datos propios de una petición deben limitar su alcance a esa petición o restablecerse explícitamente.
Los límites de las cifras publicadas
Las cifras añadidas a este artículo el 25 de agosto de 2026 indican entre 75 y 280 ms con un proceso en caliente, ya cargado en memoria. También mencionan siete días de registros de producción: 133 peticiones tras más de veinte minutos sin tráfico, todas atendidas en menos de 615 ms.
La configuración de producción observada el 11 de julio de 2026 era una instancia flex-1gb de Laravel Cloud con hibernación activada. Un periodo sin tráfico hace posible una reactivación, pero no demuestra que la instancia estuviera hibernando en cada una de esas peticiones.
Sin los registros y su protocolo de medición, la duración publicada sigue siendo difícil de interpretar. Una medición dentro de la aplicación puede excluir la reactivación de la instancia, el procesamiento del proxy y el recorrido de red hasta el navegador. Estos números no permiten saber cuánto espera un visitante.
En su anuncio del 1 de junio de 2026, Laravel Cloud indica un tiempo de reactivación inferior a 500 ms para toda la stack. El proveedor afirma haber reducido ese tiempo a una vigésima parte del de la generación anterior, con entre una y cuatro vCPU según la demanda.
Las cifras de aubia.dev no bastan para confirmar la afirmación del proveedor. No hay una comparación de este sitio con PHP-FPM.
El renderizado en el servidor de Inertia
Una página React renderizada solo en el cliente depende de la carga y ejecución de JavaScript para mostrar su contenido. No todos los rastreadores lo ejecutan, y los que lo hacen pueden aplazar ese trabajo.
Con el SSR de Inertia v3, Laravel envía la página y sus datos a un proceso Node. Node renderiza los componentes React y devuelve HTML a Laravel, que lo incluye en la respuesta mediante la vista Blade.
El contenido y los metadatos de la página están así disponibles en la respuesta HTML inicial. El renderizado lleva tiempo: Laravel debe esperar el resultado de Node antes de enviar la página.
En el navegador, React hidrata el HTML recibido para que los botones, el formulario y los demás componentes sean interactivos.
El diagrama recorre una primera visita, desde la petición del navegador hasta la hidratación.
El contador de la lista de espera no forma parte de este primer renderizado. Llega mediante una petición posterior, descrita más abajo.
En desarrollo, el plugin Vite de Inertia gestiona el SSR sin tener que arrancar un servidor de renderizado aparte. En producción, el proceso Node sigue separado del servidor PHP y debe mantenerse en funcionamiento junto a él.
La configuración del SSR se declara en config/inertia.php:
// config/inertia.php, fragmento
'ssr' => [
'enabled' => (bool) env('INERTIA_SSR_ENABLED', true),
'runtime' => env('INERTIA_SSR_RUNTIME', 'node'),
'url' => env('INERTIA_SSR_URL'), // dirección interna predeterminada omitida
'ensure_bundle_exists' => (bool) env('INERTIA_SSR_ENSURE_BUNDLE_EXISTS', true),
'throw_on_error' => (bool) env('INERTIA_SSR_THROW_ON_ERROR', false),
],
Con ensure_bundle_exists, la ausencia del bundle SSR detiene el intento de renderizado: Inertia devuelve null sin emitir un evento de fallo. Si un intento de renderizado falla, Inertia emite SsrRenderFailed. Ningún listener de la aplicación gestiona ese evento en este sitio.
El ajuste throw_on_error a false permite entonces recurrir al renderizado en el cliente. La vista app.blade.php conserva un título y una descripción genéricos, pero el contenido y los metadatos específicos de la página dependen de JavaScript.
Prefiero esa alternativa a una página de error durante una caída transitoria del SSR. Tiene un coste: el contenido puede aparecer más tarde y un rastreador que no ejecute JavaScript deja de recibir el contenido esperado. Sin un listener, estos fallos tampoco tienen una monitorización específica en la aplicación.
El code splitting con Rolldown
Del lado del build, aubia.dev utiliza Vite 8. El bundler es Rolldown, escrito en Rust e integrado de forma nativa en Vite.
El code splitting divide el JavaScript compilado en varios archivos que se cargan por separado. Se configura explícitamente en los ajustes de build:
// vite.config.ts
build: {
rolldownOptions: {
output: {
codeSplitting: {
minSize: 20_000,
groups: [
{
name: 'react',
test: /[\\/]node_modules[\\/](react|react-dom|scheduler)[\\/]/,
priority: 50,
},
{
name: 'motion',
test: /[\\/]node_modules[\\/](motion|motion-dom|motion-utils)[\\/]/,
priority: 40,
},
{
name: 'fontawesome',
test: /[\\/]node_modules[\\/]@fortawesome[\\/]/,
priority: 30,
},
{
name: 'inertia',
test: /[\\/]node_modules[\\/]@inertiajs[\\/]/,
priority: 20,
},
{
name: 'radix',
test: /[\\/]node_modules[\\/]@radix-ui[\\/]/,
priority: 10,
},
],
},
},
},
},
Se declaran cinco grupos: React y su scheduler, las animaciones Motion, los iconos FontAwesome, el cliente Inertia y los componentes Radix. Esta separación facilita reutilizar archivos en caché entre despliegues si los archivos generados no cambian. No garantiza que los archivos de dependencias sean idénticos después de cada build.
El build de producción añade un bundle SSR dedicado, producido por vite build --ssr.
El contador tras el renderizado inicial
El contador de la lista de espera obtiene el total de aubia.cloud, con una caché en el servidor. Si la caché está vacía, esa llamada de red puede retrasar la respuesta.
El middleware HandleInertiaRequests comparte por ello el contador como una prop diferida. El navegador solicita su valor en una petición adicional tras el renderizado inicial:
// app/Http/Middleware/HandleInertiaRequests.php
'waitlist' => [
'count' => Inertia::defer(fn(): ?int => app(FetchWaitlistCountQuery::class)->run(), rescue: true),
],
El renderizado inicial no espera esa llamada. Si falla la propia caché, rescue: true omite la prop del payload sin hacer fallar la petición diferida. El componente conserva entonces su estado visual neutro.
La petición adicional del contador sale una vez mostrada la página.
Laravel responde en JSON, sin pasar por Node: el total procede de la caché del servidor o de aubia.cloud.
La elección de FrankenPHP
El archivo config/octane.php utiliza RoadRunner por defecto cuando no se define ninguna variable de entorno:
// config/octane.php
'server' => env('OCTANE_SERVER', 'roadrunner'),
RoadRunner es otro servidor compatible con los workers de Octane. Swoole también está disponible, pero requiere una extensión PHP compilada.
Elegí FrankenPHP por su modo worker y la integración de Caddy en el mismo binario. Su compatibilidad con HTTP/3 y los certificados automáticos también figuraban en el registro de decisión de arquitectura del proyecto.
Esas dos últimas capacidades no se utilizan en aubia.dev: el proxy de producción termina TLS y HTTP/3 antes de llegar a FrankenPHP. El binario integrado simplifica la parte PHP del despliegue, pero el SSR sigue necesitando su proceso Node separado.
Cambiar a RoadRunner requeriría instalar su binario, adaptar la configuración y el arranque, y probar el despliegue. La variable OCTANE_SERVER selecciona el driver; no sustituye ese trabajo.
Octane evita reinicializar Laravel en cada petición y el SSR proporciona el contenido React en la respuesta HTML inicial. Las cifras publicadas siguen siendo referencias históricas: sin registros verificables ni un protocolo de medición, no demuestran una mejora frente a PHP-FPM ni un tiempo completo de reactivación.
Esta obra se cuenta aquí, artículo a artículo. Lo que viene después depende de lo que usted diga de ella.
Únase a la lista de espera