27 de abril de 2024

Cómo optimizar el rendimiento de tu proyecto Laravel en 2026 (Octane + FrankenPHP)

Foto de Marco Orta Marco Orta | 9 mins de lectura
Compartir
Gráfica de rendimiento mostrando mejoras de respuesta en una aplicación Laravel
Tabla de Contenidos

    Introducción

    Laravel ha ganado una inmensa popularidad gracias a su sintaxis elegante y características robustas, pero a medida que tu proyecto escala, asegurar un rendimiento óptimo se vuelve crucial. En esta guía actualizada para Laravel 13 (2026) repasamos las técnicas que mueven la aguja de verdad: desde patrones clásicos como eager loading e índices, hasta los grandes saltos modernos como Laravel Octane con FrankenPHP, Pulse para monitoreo y colas optimizadas.

    Spoiler: si solo vas a aplicar una mejora de esta guía, que sea Octane con FrankenPHP: tu aplicación deja de arrancar desde cero en cada petición sin reescribir tu código (aunque sí toca auditar el estado compartido, más abajo).

    Octane + FrankenPHP: la mejora de rendimiento más grande de 2026

    El cambio más importante para Laravel en los últimos años no es una nueva sintaxis, es cómo se sirve la aplicación. Por defecto, PHP arranca la aplicación entera en cada petición (PHP‑FPM). Laravel Octane mantiene tu aplicación en memoria entre peticiones, y entre los drivers disponibles, FrankenPHP es el favorito en 2026 por su balance entre rendimiento y simplicidad.

    Qué ganancia esperar

    No hay una cifra universal, y conviene desconfiar de quien la dé: depende de cuánto pesa el arranque de tu aplicación frente al tiempo que pasa esperando a la base de datos o a APIs externas.

    • PHP‑FPM clásico arranca el framework completo en cada petición.
    • Octane (FrankenPHP, Swoole o RoadRunner) lo arranca una vez por worker y lo reutiliza. Las apps con arranque pesado y poco I/O son las que más ganan; si tu app pasa casi todo el tiempo esperando queries lentas, la mejora es mucho menor y lo primero son los índices.
    • FrankenPHP suma early hints, SSL automático y compresión Zstandard.

    Mide con tu propia app, antes y después, contra un endpoint real, en vez de fiarte de benchmarks ajenos.

    Instalación

    composer require laravel/octane
    php artisan octane:install --server=frankenphp
    php artisan octane:frankenphp
    

    Esto te deja un servidor moderno corriendo en el puerto 8000. En producción, FrankenPHP ofrece además un binario standalone que sirve tu aplicación entera (incluido el contenido estático) en un solo ejecutable.

    Lo que tienes que auditar al activar Octane

    Como Laravel se queda en memoria, hay tres patrones que pasaban sin problema con FPM pero ahora pueden filtrar estado entre peticiones:

    1. Singletons que guardan datos del request: revisa todos tus service providers y Container::singleton().
    2. Variables estáticas con estado: si guardas datos en una propiedad static de una clase, vivirán entre peticiones.
    3. Bindings que dependen del request: usa app()->scoped() para bindings que deben renovarse cada request, no singleton().

    Laravel ofrece eventos como RequestReceived, RequestHandled y WorkerStarting para resetear estado cuando lo necesites:

    // app/Providers/AppServiceProvider.php
    use Laravel\Octane\Events\RequestReceived;
    use Illuminate\Support\Facades\Event;
    
    Event::listen(RequestReceived::class, function () {
        // Reiniciar estado por request
    });
    

    Cuándo usarlo (y cuándo no)

    • ✅ Vale completamente la pena si manejas más de 500 req/min de tráfico real.
    • ✅ APIs públicas, dashboards con muchos usuarios concurrentes, e‑commerce de alto tráfico.
    • ⚠️ Para una landing con 100 visitas/día no notarás diferencia.
    • ⚠️ Si tienes mucho código legacy con estado global, audita antes de activar Octane en producción.

    Optimización de Consultas

    Carga Previa (Eager Loading)

    Utiliza la carga previa para reducir el número de consultas a la base de datos. Por ejemplo, al recuperar un post y sus comentarios, es preferible cargar previamente los comentarios para evitar el problema de consulta N+1.

    Antes:

    $posts = Post::all();
    foreach ($posts as $post) {
        $comments = $post->comments;
    }
    
    

    Después:

    $posts = Post::with('comments')->get();
    
    

    Indexación de la Base de Datos

    Asegúrate de que las tablas de tu base de datos estén apropiadamente indexadas para acelerar la ejecución de consultas. Utiliza las migraciones de Laravel para definir índices.

    Schema::table('posts', function (Blueprint $table) {
        $table->index('user_id');
    });
    
    

    Optimización de Código

    Aprovechar el Caching

    Cachéa datos que se acceden frecuentemente para reducir la carga en tu base de datos. Laravel ofrece una API limpia para el caching.

    $posts = Cache::remember('recent_posts', 60, function () {
        return Post::latest()->take(10)->get();
    });
    
    

    Optimizar Rutas

    Minimiza el número de rutas en tu aplicación y usa controladores con recursos para mantener tu archivo de rutas limpio y organizado.

    use App\Http\Controllers\PostController;
    
    Route::resource('posts', PostController::class);
    
    

    Optimización de la Base de Datos

    Uso de Transacciones en la Base de Datos

    Envuelve las operaciones de base de datos en transacciones para asegurar la consistencia de los datos y prevenir commits innecesarios.

    DB::transaction(function () {
        // Operaciones de base de datos
    });
    
    

    Colas de Base de Datos

    Transfiere tareas que consumen mucho tiempo a colas para mejorar los tiempos de respuesta. Configura un trabajador de cola para procesar estos trabajos de manera asíncrona.

    php artisan queue:work
    
    

    Uso Eficiente de Cláusulas WHERE

    Problema: Mezclar where y orWhere sin agrupar devuelve filas que no esperabas en cuanto la consulta lleva más condiciones, porque en SQL AND se evalúa antes que OR.

    Solución: Agrupa los orWhere en un closure, como pide la documentación de Laravel. No es una mejora de velocidad —con una sola condición, las dos formas generan la misma consulta—, sino de corrección. Lo que sí acelera un WHERE es que las columnas filtradas tengan índice.

    Sin agrupar (falla al añadir otro filtro):

    $posts = DB::table('posts')->where('user_id', $userId)->where('status', 'published')->orWhere('published_at', '>=', now())->get();
    // user_id = ? AND status = ? OR published_at >= ?  → trae posts de otros usuarios
    
    

    Agrupado:

    $posts = DB::table('posts')->where('user_id', $userId)->where(function ($query) {
        $query->where('status', 'published')->orWhere('published_at', '>=', now());
    })->get();
    
    

    Evitar SELECT *

    Problema: Usar SELECT * recupera todas las columnas de una tabla, incluso si solo necesitas unas pocas.

    Solución: Selecciona solo las columnas que necesitas para reducir la transferencia de datos y mejorar la velocidad de la consulta.

    Limitar y Desplazar con Precaución

    Problema: Usar limit y offset sin un índice adecuado puede llevar a paginación lenta.

    Solución: Usa limit y offset en combinación con ordenar por columnas indexadas.

    Procesamiento por Lotes para Actualizaciones o Eliminaciones

    Problema: Las operaciones grandes de actualización o eliminación pueden causar bloqueos y problemas de rendimiento.

    Solución: Utiliza el procesamiento por lotes para actualizar o eliminar registros en porciones más pequeñas.

    Manejo Eficiente de Grandes Conjuntos de Datos

    Chunking

    Cuando se manejan grandes conjuntos de datos, es importante optimizar tu código para manejarlos de manera eficiente. En lugar de cargar todos los registros en memoria de una vez, utiliza el método chunk() para procesar los registros en lotes más pequeños. Esto previene el agotamiento de la memoria.

    Post::chunk(200, function ($posts) {
        foreach ($posts as $post) {
            // Procesa cada post
        }
    });
    
    

    Patrón Observador

    El patrón observador es una herramienta poderosa para desacoplar componentes e implementar comportamiento impulsado por eventos.

    Patrón Observador: Laravel ofrece una manera elegante de implementar el patrón observador. Puedes usar observadores para desencadenar automáticamente acciones cuando ocurren eventos específicos en tus modelos Eloquent.

    class PostObserver {
        public function created(Post $post) {
            // Código a ejecutar después de que se crea un nuevo post
        }
    }
    
    // En tu proveedor de servicios o modelo
    Post::observe(PostObserver::class);
    
    

    Difusión de Eventos

    Utiliza la difusión de eventos de Laravel para enviar actualizaciones en tiempo real a tu aplicación. Esto es especialmente útil para aplicaciones de chat, notificaciones y actualizaciones en vivo.

    // Crea un evento
    event(new NewComment($comment));
    
    // Escucha el evento y transmítelo
    
    

    Optimización del Rendimiento de Páginas con Imágenes

    Implementación de Carga Perezosa de Imágenes

    Implementa la carga perezosa para imágenes usando el atributo loading="lazy" en HTML. Esto pospone la carga de imágenes que están fuera de pantalla, mejorando los tiempos de carga de la página, especialmente en páginas con muchas imágenes.

    Optimización de Migraciones de Bases de Datos

    Optimización de Migraciones de Bases de Datos en Grandes Proyectos

    Cuando trabajas con un gran número de migraciones, considera optimizarlas. Divide los cambios en migraciones pequeñas y enfocadas, y cuando se acumulen cientos, compáctalas con php artisan schema:dump --prune, que las sustituye por un único archivo SQL con el esquema actual.

    Ejemplo de optimización de migraciones de bases de datos dividiéndolas en lotes más pequeños:

    Paso 1: Crear una Migración Inicial

    Supongamos que tienes una migración inicial para crear una tabla “posts”:

    php artisan make:migration create_posts_table
    
    

    En el archivo de migración generado, define el esquema para la tabla “posts”:

    public function up()
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->id();
            $table->string('title');
            $table->text('content');
            $table->timestamps();
        });
    }
    
    

    Paso 2: Dividir las Migraciones

    Ahora, a medida que tu aplicación evoluciona, puede que necesites hacer cambios adicionales en la tabla “posts”. En lugar de añadir todos estos cambios en una sola migración, divídelos en migraciones más pequeñas y enfocadas.

    Por ejemplo, si deseas añadir una columna “published_at” a la tabla “posts”, crea una migración dedicada para este cambio:

    php artisan make:migration add_published_at_to_posts_table
    
    

    En el archivo de migración generado, define la modificación del esquema:

    public function up()
    {
        Schema::table('posts', function (Blueprint $table) {
            $table->timestamp('published_at')->nullable();
        });
    }
    
    

    De esta manera, tienes migraciones separadas para cada cambio de esquema, lo que facilita la gestión y el rollback de cambios cuando sea necesario.

    Paso 3: Ejecutar las Migraciones

    Ejecuta tus migraciones como de costumbre:

    php artisan migrate
    
    

    Al dividir las migraciones en lotes más pequeños, mantienes un mejor control sobre el esquema de tu base de datos y puedes aplicar cambios de manera incremental, mejorando la manejabilidad general de tu aplicación Laravel.

    Optimización de Vistas en Laravel

    Minimizar la Lógica en Plantillas Blade

    Problema: La lógica compleja y los cálculos intensivos en las plantillas Blade pueden afectar la velocidad de renderizado.

    Solución: Minimiza la lógica en Blade trasladando operaciones complejas a controladores o servicios.

    
    @foreach ($posts as $post)
        @if (strlen($post->title) > 50)
            

    {{ substr($post->title, 0, 50) }}...

    @else

    {{ $post->title }}

    @endif @endforeach @foreach ($posts as $post)

    {{ optimizeTitle($post->title) }}

    @endforeach

    Uso de Compositores de Vistas

    Problema: Repetir código en múltiples vistas puede volverse difícil de mantener.

    Solución: Utiliza compositores de vistas para compartir datos a través de las vistas y mantener tu código DRY (No Repetirte).

    // En un proveedor de servicios
    View::composer(['posts.index', 'posts.show'], function ($view) {
        $view->with('categories', Category::all());
    });
    
    

    Caché de Vistas

    Problema: Renderizar contenido dinámico para cada solicitud puede ralentizar los tiempos de carga de las páginas.

    Solución: Cachear los datos que alimentan la vista, o el HTML ya renderizado de un fragmento. Blade no trae ninguna directiva para esto (ni @cache ni nada parecido); lo nativo es Cache::remember, en el controlador o en un view composer:

    // En el controlador: guarda el HTML del fragmento una hora
    $sidebar = Cache::remember('sidebar.html', 3600, function () {
        return view('partials.sidebar', ['categories' => Category::all()])->render();
    });
    
    return view('posts.index', compact('posts', 'sidebar'));
    
    
    {!! $sidebar !!}
    

    Si prefieres escribirlo como directiva en la plantilla, el paquete de terceros ryangjchandler/blade-cache-directive añade un @cache('clave', ttl) … @endcache que hace lo mismo por debajo. Es un paquete aparte, no parte de Laravel.

    Uso de Vistas Parciales

    Problema: Las plantillas Blade largas y complejas pueden volverse difíciles de gestionar.

    Solución: Divide las vistas en parciales más pequeños y reutilizables para mejorar la organización.

    
    @include('partials.header')
    
    
    

    Minimizar Solicitudes Externas

    Problema: Cargar múltiples recursos externos (scripts, hojas de estilo) puede ralentizar los tiempos de carga de las páginas.

    Solución: Minimiza las solicitudes externas combinando scripts y hojas de estilo, y cargándolos de manera asíncrona.

    <!-- Cargar scripts de forma asíncrona -->
    <script async src="app.js"></script>
    
    <!-- Combinar y minimizar hojas de estilo -->
    <link rel="stylesheet" href="styles.css">
    
    

    Precargar Recursos Críticos

    Problema: La carga tardía de recursos críticos puede afectar la percepción del rendimiento.

    Solución: Utiliza la etiqueta <link rel="preload"> para precargar recursos críticos.

    Monitoreo en producción: Pulse y Telescope

    No puedes optimizar lo que no mides. En 2026 Laravel ofrece dos herramientas de primer nivel:

    • Laravel Pulse: dashboard ligero de producción con métricas de servidor, requests lentos, jobs en cola y queries pesadas. Pesado en producción solo cuando está activamente recolectando — está pensado para correr siempre.
    • Laravel Telescope: herramienta de debugging para desarrollo, mucho más detallada (cada request, query, mail, job, exception). Útil en staging, no en producción.
    composer require laravel/pulse
    php artisan pulse:install
    php artisan migrate
    

    Visita /pulse para ver el dashboard. Combinado con un APM externo (Sentry, Datadog, New Relic) tendrás visibilidad completa.

    Optimización de Colas (Queues)

    Para Laravel 13, las recomendaciones modernas en colas son:

    • Redis como driver (en lugar de database) para alto throughput.
    • Horizon para dashboard y supervisión de workers Redis. composer require laravel/horizon.
    • Preferir queue:work sobre queue:listen en producción: no recarga el código entre jobs, mucho más eficiente.
    • Configurar timeouts, retries y backoff explícitos por job con los atributos PHP nativos de Laravel 13:
    use Illuminate\Bus\Queueable;
    use Illuminate\Foundation\Bus\Dispatchable;
    
    #[\Illuminate\Queue\Attributes\Tries(3)]
    #[\Illuminate\Queue\Attributes\Backoff([1, 5, 10])]
    #[\Illuminate\Queue\Attributes\Timeout(120)]
    class ProcessReport implements ShouldQueue
    {
        use Queueable, Dispatchable;
    
        public function handle(): void { /* ... */ }
    }
    
    • Usar batches (Bus::batch([...])) para procesar trabajos en grupo con callbacks de progreso/error.

    Caché distribuido y rate limiting

    Para aplicaciones a escala, dos consejos rápidos pero de gran impacto:

    • Redis o Memcached como driver de caché en lugar de file o database. Al vivir en memoria, lee mucho más rápido que un archivo o una tabla y aguanta muchas operaciones concurrentes.
    • Rate limiting con RateLimiter: protege tu API y reduce carga inútil de bots.
    RateLimiter::for('api', function (Request $request) {
        return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
    });
    

    Frontend: lazy loading y assets servidos por la CDN

    El último 20 % del rendimiento percibido por el usuario suele estar en el frontend, no en el backend. Recomendaciones:

    • Lazy loading de imágenes con loading="lazy".
    • Vite (incluido por defecto en Laravel 13) en lugar de Mix; hace tree‑shaking y splitting automáticos.
    • Servir assets desde CDN (Cloudflare, Bunny, CloudFront). Configura ASSET_URL en .env.
    • Comprimir respuestas con gzip/Brotli/Zstandard a nivel del servidor (FrankenPHP lo hace automático).

    Conclusión

    La optimización de Laravel es un proceso continuo, pero en 2026 el camino está bastante claro:

    1. Caché y eager loading primero — son baratos y casi siempre es lo primero que aparece al medir.
    2. Índices y consultas después.
    3. Octane con FrankenPHP cuando el tráfico lo justifique — el salto cualitativo más grande.
    4. Horizon, Pulse y un APM para monitorear cuando crezcas.

    Recuerda: optimizar es un viaje, no una tarea única. Mide, mejora, vuelve a medir.

    Si vas a llevar tu proyecto al siguiente nivel, también te puede interesar Cómo crear una API REST con Laravel 13 y Cómo construir un agente de IA con Laravel y MCP.

    Compartir

    Buscar

    Etiquetas

    Migración IA PHP JavaScript Laravel Tutorial Desarrollo Web Upgrade Buenas Prácticas Seguridad SEO Backend Claude Laravel 13 TypeScript