Laravel

6 Tips and Tricks To Speed Up Your Laravel Website

Harry 6 min read
6 Tips and Tricks To Speed Up Your Laravel Website
Table of Contents

There's nothing more frustrating than a website that takes time to load. If this is happening to your users, they are going to leave your website before you've even had a chance to convince them otherwise.

Here are a few quick tips for speeding up your Laravel websites and applications.

1. Review slow queries

An easy way to see all the queries that are running for each page request is by installing Laravel Debugbar.

Laravel Debugbar Queries Ouput

Using Laravel Debugbar will allow you to dig into each query, review its binding, and review which queries are taking longer than necessary.

If a query is taking a long time to process. Check if the table its querying has the necessary indexes defined. An index can be added to a column via a database migration and calling the ->index($columnName) method.

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

To check if a query is hitting an index, copy the SQL and prepend EXPLAIN to the query before running it in your SQL tool. This will tell you if your query is using an index or not.

Query that is using an index

If you see no "Using index condition" then that's your cue to add an index for the fields being queried on, otherwise to rewrite the query or forgo it completely.

2. Caching

Caching is a technique that can be applied to various operations that don't need to return the latest live data. Whether it's an API request or a slow query - throwing caching in front will make a huge difference.

In Laravel, we can cache results either forever or for a set amount of time before the operation is executed again.

Eg: Caching forever can be done with the Cache Facade.

use Illuminate\Support\Facades\Cache;

...

// Cache forever until busted
$subscriptionPlans = Cache::rememberForever('subscription_plans', function () {
    return SubscriptionPlan::query()
        ->orderBy('price')
        ->get()
        ->groupBy('name');
});

// Bust the cache
Cache::forget('subscription_plans');

Or for example, cache for 2 days before the cache is cleared and the value is regenerated.

use Illuminate\Support\Facades\Cache;
use Carbon\CarbonInterval;

...

// Cache value for 2 days
$subscriptionPlans = Cache::remember('subscription_plans', CarbonInterval::days(2), function () {
    return SubscriptionPlan::query()
        ->orderBy('price')
        ->get()
        ->groupBy('name');
});

Consider employing this technique for expensive queries that your users are hitting frequently. Good ones to start with are COUNT queries which can bring a database to its knees if used on tables with a large number of rows.

3. Select only the data you need in your queries

When models are populated via the database, unless you say otherwise, Eloquent is performing a SELECT * query to retrieve all the rows from the database. This isn't usually a problem until you start pulling in, let's say 1,000 rows each with 200 fields when all you were after was a title.

All this is doing is putting unnecessary strain on the database and subsequentially, the network which now has to transfer more data than necessary.

To mitigate this. When calling the Eloquent builder, specify which columns you require with the select() method.

$subscriptionPlans = SubscriptionPlan::query()
    ->select(['id', 'name', 'price'])
    ->get();

If all you're after is a single value, then ->value($column) or ->pluck($column) are your answer.

// Return the name of the first matching row
$subscriptionPlans = SubscriptionPlan::query()
    ->value('name'); // returns 'Free'

$subscriptionPlans = SubscriptionPlan::query()
    ->pluck('name'); // returns an array (collection) of all matching rows with 'name' as the value.

4. Eager Loading

Eager loading resolves one of the most common, and easily fixed issues new developers usually face when starting out with Laravel.

The N+1 issue is where we're calling an additional query for each model we're looping over. For example, when looping through a list of users we may want to also show associated data from another table.

It could look something like this:

$users = User::query()
    ->take(10)
    ->get(); // 1 Query

foreach ($users as $user) {
    // +1 query every iteration, when accessing the user invoice relationship.
    echo $user->name . ' paid at: ' . $user->invoice->paid_at . PHP_EOL;
}

The above example will be executing 11 queries under the assumption we're retrieving 10 users.

Using eager loading, this can be reduced to 2 queries regardless of how many users are retrieved.

$users = User::query()
    ->with('invoice')
    ->take(10)
    ->get();
    
...

Or loading afterwards:

$users = User::query()
    ->take(10)
    ->get();
        
$users->load('invoice');

...

5. Use Queues

One common mistake is sending an email or making a third-party call at the time of request. This could be a welcome email when a user signs up to your service. However, in order to dispatch that email you will need to contact a third party service to handle it for you. This means the user is waiting for your server to contact that said third-party.

Now what happens if the third-party is getting hammered, is having technical issues, or there's an error with the payload you're sending? You are passing all of these issues onto your user when your own server is working fine.

Using queues allows you to throw a task to be performed in the background. To move a task into a job you can use artisan to help you get started:

$ php artisan make:job SendRegistrationEmail

Define any properties you want to pass into the job by defining them in its constructor.

public function __construct(protected User $user)
{
}

Inside the handle function you can add your logic.

public function handle(): void
{
    Mail::to($this->user->email)->send(new UserRegisteredEmail($user));
}

And finally, to create your job ie, in your controller.

dispatch(new SendRegistrationEmail($user)); // Run on queue (if configured)

dispatch_sync(new SendRegistrationEmail($user)); // Runs procedurally, at time of request

The one caveat is you do need to configure your server to runs jobs. I'll refer you to the Laravel documentation to read up on running queues, as there are multiple ways of doing this; the documentation explains it best.

6. Maybe it's just your server

Take a look at your version of PHP, the specs available to you, and whether or not you're getting value for money with your current hosting. Your website can be hindered by dated hardware so if this sounds like it could be you - take a look at Digital Ocean. By using my link you'll receive $100 in credit to freely flesh out your application to see if it makes a difference for you.

DigitalOcean | Cloud Hosting for Builders
Simple, scalable cloud computing solutions built for startups and small-to-midsize businesses.

If you're not accustomed to setting up everything yourself from scratch. Laravel Forge can provision a droplet with everything you need to get going including deploying your code.

Digital Ocean also provides managed database instances if you want to separate the database into its own resource. This does come at an additional cost though.

If you would like to try out another option, I also recommend Hetzner. I recently moved my servers to Hetzner as they offer more value for money.

Use the link below to get €⁠20 credit to play with.

Hetzner Cloud

Conclusion

Interested in more Laravel tricks to speed up your website? Check out More Laravel Optimisation Tricks:

More Laravel Optimisation Tricks
In my first blog post, I went over 5 tricks you can use to improve the speed of your Laravel websites. Below are a few additional tricks that may not specifically improve your website’s performance, but will improve your website’s reliability. Query Chunking Loading too much data all at once

Share
Comments
More from Harrk Blog

Harrk Blog

Programming nonsense, tutorials, and random musings about games and technology.

Great! You’ve successfully signed up.

Welcome back! You've successfully signed in.

You've successfully subscribed to Harrk Blog.

Success! Check your email for magic link to sign-in.

Success! Your billing info has been updated.

Your billing was not updated.