27 de abril de 2024

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

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

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. Pasas de 400 a 6 000+ req/s con el mismo código.

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.

Benchmarks típicos

  • PHP‑FPM clásico: 300–500 req/s en una app Laravel mediana.
  • Octane + Swoole/RoadRunner: 2 000–4 000 req/s (5–8× más).
  • Octane + FrankenPHP: 4 000–6 000+ req/s (10–15× más), early hints, SSL automático y compresión Zstandard.

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.

Route::resource('posts', 'PostController');

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: El uso ineficiente de cláusulas WHERE puede conducir a consultas lentas.

Solución: Sé específico con las condiciones WHERE y usa índices cuando sea posible.

Ineficiente:

$posts = DB::table('posts')->where('status', '=', 'published')->orWhere('published_at', '>=', now())->get();

Eficiente:

$posts = DB::table('posts')->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 las migraciones en lotes más pequeños o utiliza paquetes como laravel-doctrine/dbal para una gestión de esquemas más eficiente.

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 vistas completas o partes de vistas para reducir la sobrecarga de renderizado.


@cache('sidebar', 60)
    
@endcache


@cacheSection('sidebar', 60)
    
@endcacheSection

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. Es 10–100× más rápido para reads y aguanta miles de 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 gratis y dan el 30 % de la mejora.
  2. Índices y consultas después — el otro 30 %.
  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