Faster Laravel: Surprising Optimization Tips

Published on by

Faster Laravel: Surprising Optimization Tips image

When we started working on our test management tool Testmo a few years ago and selected Laravel for our backend implementation, there were a couple of surprising performance issues we've noticed.

For Testmo our goal is to answer most server requests in 100ms or less. You might be wondering why this is important to us when most apps spend significantly more time rendering the frontend in the browser and executing JavaScript (which is also true for Testmo, especially with our complex UI for test case management with multiple panes and dynamic folders).

It's pretty simple: slow server-side performance will trickle down to all parts of the stack, so optimizing this part will have very positive effects on the entire app. Testmo customers are also heavily using our API as part of our test automation features. So optimizing the server-side performance also helps us to significantly reduce server load to better scale the app with fewer servers & resources.

You can find many generic Laravel optimization articles out there describing the basics of configuring caching, how to build faster database queries etc. Instead, in this article I want to share three specific optimizations for Laravel Blade views that surprised us when we started using Laravel and that resulted in drastic performance improvements.

Blade loops can be surprisingly slow

A significant part of server-side performance is based on how fast you can transform database query results to rendered content. In Laravel's case you would usually use Blade views to render your content and send results to browsers. When we initially started working with Blade and its various loop directives, we were surprised that loops are quite slow. The reason for this is that they often provide additional features that might be useful in some cases, but are unnecessary slow when you don't need them.

Let's look at Laravel Blade's @foreach loop directive as an example. You would use @foreach very often in views to iterate over data and render content, so its performance is quite critical. When we started using Laravel and measured the view performance, this directive was surprisingly slow. We expected that it maps more or less directly to PHP's foreach loops, but when you look at Laravel's implementation, it adds a lot of overhead to add the loop variable.

This feature might be useful from time to time, but it adds significant overhead in the majority of cases you don't need it. The solution for us was quite simple: we don't use @foreachat all, but added our own @loop directive, which directly maps to PHP's native foreach:

Blade::directive('loop', function ($expression) {
return "<?php foreach ($expression): ?>";
});
 
Blade::directive('endloop', function ($expression) {
return "<?php endforeach; ?>";
});

Don't include & render many views

Views are a great way to reuse common UI elements in different parts of the app without repeating your frontend code. Let's say you are rendering the avatar of users in the UI as part of various data tables, sidebar sections and in other places. Similar to how we render user avatars to indicate contributors and owners of exploratory testing sessions in Testmo:

Ideally we could use regular Blade views to reuse and include such common UI elements, as rendering the avatar takes >10 lines of code (e.g. to fallback to user initials for users who haven't uploaded an avatar yet). But it turns out that using many Blade views in a request is a very bad idea, as including views can be very slow. So slow in fact that rendering larger tables with many UI elements can add 100s of milliseconds of overhead, which would be unacceptable for most apps.

For Testmo we try to keep the number of rendered views per request to less than 15 on most pages. This unfortunately rules out using views for common elements that could be rendered dozens of times. Instead, we've come up with an alternative approach with a good balance of dev productivity & app performance, a concept we call partials internally.

Partials are pre-rendered views that we compile & store during development and directly inject into views via a simple blade directive. This way we can easily reuse common UI elements and only write them once without the overhead of including full views. Instead, our internal @partial blade directive loads the pre-rendered code and outputs it in-place. So our views can load partials similar to including a full view, but when you look at the compiled & cached view output, it directly includes the pre-rendered code without any overhead.

@partial('avatars.user', [
'user' => $user,
'size' => 'table',
'tooltip' => $user->name
])

Including views is still slow & error-prone

Using partials instead of views was a big performance win, but including views was still unreasonable slow. But there was another issue we didn't like about Blade's default @include behavior: included views automatically inherit all data available in the parent view. So it becomes difficult to control which parameters are passed to views, which can lead to bugs and unexpected behavior quickly if you are not careful.

We noticed quite quickly that we didn't like this default behavior, so we looked into ways to improve this. When we reviewed Laravel's default @include implementation, we noticed that changing this behavior also had the potential for significant speed improvements.

In Testmo we don't use @include at all anymore. Instead, we've built our own lightweight alternative we call @require. Our own directive doesn't make all parameters available in sub views anymore, which makes it easier for us to avoid bugs, improves testability and helps us quickly understand which parameters are used & available. It also avoids using PHP's get_defined_vars function, which causes a lot of the performance issues with Laravel's default @include implementation.

@php
// Not automatically available in sub view
$completed = true;
@endphp
 
@require('automation.results.header', [
'results' => $results,
'states' => $states,
])

The three above mentioned optimizations combined resulted in huge performance differences for non-trivial pages (sometimes 100s of milliseconds). Laravel's default behavior and Blade implementation provides a lot of useful features. If you don't need all these features and prefer faster views, it can make sense to use simpler & faster implementations though. Hopefully this article gives you some ideas on how to optimize your own Laravel apps.

This guest posting was written by Dennis Gurock, one of the founders of Testmo. Testmo is built using Laravel and helps teams manage all their software tests in one modern platform. If you are not familiar with QA tools, Testmo recently published various tool guides to get started:

Eric L. Barnes photo

Eric is the creator of Laravel News and has been covering Laravel since 2012.

Filed in:
Cube

Laravel Newsletter

Join 40k+ other developers and never miss out on new tips, tutorials, and more.

image
CodeRabbit

CodeRabbit is an AI-powered code review tool that specializes in PHP and Laravel, running PHPStan and offering automated PR analysis, security checks, and more

Visit CodeRabbit
Curotec logo

Curotec

World class Laravel experts with GenAI dev skills. LATAM-based, embedded engineers that ship fast, communicate clearly, and elevate your product. No bloat, no BS.

Curotec
Bacancy logo

Bacancy

Supercharge your project with a seasoned Laravel developer with 4-6 years of experience for just $2500/month. Get 160 hours of dedicated expertise & a risk-free 15-day trial. Schedule a call now!

Bacancy
Laravel Forge logo

Laravel Forge

Easily create and manage your servers and deploy your Laravel applications in seconds.

Laravel Forge
Tinkerwell logo

Tinkerwell

The must-have code runner for Laravel developers. Tinker with AI, autocompletion and instant feedback on local and production environments.

Tinkerwell
Cut PHP Code Review Time & Bugs into Half with CodeRabbit logo

Cut PHP Code Review Time & Bugs into Half with CodeRabbit

CodeRabbit is an AI-powered code review tool that specializes in PHP and Laravel, running PHPStan and offering automated PR analysis, security checks, and custom review features while remaining free for open-source projects.

Cut PHP Code Review Time & Bugs into Half with CodeRabbit
Join the Mastering Laravel community logo

Join the Mastering Laravel community

Connect with experienced developers in a friendly, noise-free environment. Get insights, share ideas, and find support for your coding challenges. Join us today and elevate your Laravel skills!

Join the Mastering Laravel community
Kirschbaum logo

Kirschbaum

Providing innovation and stability to ensure your web application succeeds.

Kirschbaum
Shift logo

Shift

Running an old Laravel version? Instant, automated Laravel upgrades and code modernization to keep your applications fresh.

Shift
Lucky Media logo

Lucky Media

Get Lucky Now - the ideal choice for Laravel Development, with over a decade of experience!

Lucky Media
Lunar: Laravel E-Commerce logo

Lunar: Laravel E-Commerce

E-Commerce for Laravel. An open-source package that brings the power of modern headless e-commerce functionality to Laravel.

Lunar: Laravel E-Commerce
LaraJobs logo

LaraJobs

The official Laravel job board

LaraJobs
SaaSykit: Laravel SaaS Starter Kit logo

SaaSykit: Laravel SaaS Starter Kit

SaaSykit is a Multi-tenant Laravel SaaS Starter Kit that comes with all features required to run a modern SaaS. Payments, Beautiful Checkout, Admin Panel, User dashboard, Auth, Ready Components, Stats, Blog, Docs and more.

SaaSykit: Laravel SaaS Starter Kit
MongoDB logo

MongoDB

Enhance your PHP applications with the powerful integration of MongoDB and Laravel, empowering developers to build applications with ease and efficiency. Support transactional, search, analytics and mobile use cases while using the familiar Eloquent APIs. Discover how MongoDB's flexible, modern database can transform your Laravel applications.

MongoDB

The latest

View all →
Laravel Simple RabbitMQ Package image

Laravel Simple RabbitMQ Package

Read article
Navigating Dates Elegantly with Carbon in Laravel image

Navigating Dates Elegantly with Carbon in Laravel

Read article
Memoized Cache Driver in Laravel 12.9 image

Memoized Cache Driver in Laravel 12.9

Read article
Laravel Cookie Consent image

Laravel Cookie Consent

Read article
Converting Non-Decimal Strings with Laravel's Enhanced toInteger() Method image

Converting Non-Decimal Strings with Laravel's Enhanced toInteger() Method

Read article
Herd Xdebug Toggler for Visual Studio Code image

Herd Xdebug Toggler for Visual Studio Code

Read article