Lightweight Breadcrumbs in Laravel

Breadcrumbs are important in web applications. But most of the time, it’s not the easiest to track the different levels in your URL and generate breadcrumbs from it. Now we give it a try with a simple yet elegant solution.

The Concept

In one of our projects, we had to implement breadcrumbs to help the users locating themselves. Sounds, excellent but we have found solutions mostly, that required a package. After digging deeper, we bumped into an excellent idea, which processed the request’s URL segments and generated the breadcrumbs based on that.

But we wanted to polish it a bit. We needed a dynamic solution for the labels and also we had to find out, how to build the URLs for the different labels. We decided to generate an array where the keys are the segments of the URL, and the values are the URLs of the keys. Let’s see an example:

// Route: blog/category/seo

[
    'blog' => 'blog',
    'category' => 'blog/category',
    'seo' => 'blog/category/seo',
]

Now move on and let’s see how can we generate this structure.

Writing the View Composer

Since we don’t need the breadcrumbs in every view, we deiced to put it in a view composer, because we can control in which views we share the data and which we don’t.

If you are new to view composers, check out the documentation or our blog post!

We created a dedicated composer class to keep the logic separated and the code clean. Let’s see the composer’s code:

<?php

namespace App\Composers;

use Illuminate\View\View;
use Illuminate\Http\Request;

class BreadcrumbComposer
{
    /**
     * The request instance.
     *
     * @var \Illuminate\Http\Request
     */
    protected $request;

    /**
     * Initialize a new composer instance.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return void
     */
    public function __construct(Request $request)
    {
        $this->request = $request;
    }

    /**
     * Bind data to the view.
     *
     * @param  \Illuminate\View\View  $view
     * @return void
     */
    public function compose(View $view)
    {
        $view->with('breadcrumbs', $this->parseSegments());
    }

    /**
     * Parse the request route segments.
     *
     * @return \Illuminate\Support\Collection
     */
    protected function parseSegments()
    {
        return collect($this->request->segments())->mapWithKeys(function ($segment, $key) {
            return [
                $segment => implode('/', array_slice($this->request->segments(), 0, $key + 1))
            ];
        });
    }
}

So what is happening here? In the constructor, we get the request by using Laravel’s dependency injection feature. In the compose() method, we share the data with the view files. We get the data from the parseSegments() method. We pair the segment with the proper URL fragment.

Now, the only thing we should not forget, to bind the composer the views we need. We can do it in a service provider’s boot() method.

// Push the breadcrumbs to the view
View::composer('blog.*', BreadcrumbComposer::class);
Don’t forget to import the composer at the top of your service provider. Also, note you can control your scope with the first parameter of the composer method.

Rendering the Breadcrumbs

We have nice progress, all we have to do so far to deliver the breadcrumbs in the view. Let’s take a look at the blade template:

<nav>
    <ul>
        <li class="{{ $breadcrumbs->isEmpty() ? 'is-active' : '' }}"><a href="/">Home</a></li>
        @foreach ($breadcrumbs as $key => $url)
            <li class="{{ $loop->last ? 'is-active' : '' }}">
                <a href="{{ url($url) }}">
                    @if (! $loop->last)
                        {{ ucfirst($key) }}
                    @else
                        @yield ('title')
                    @endif
                </a>
            </li>
        @endforeach
    </ul>
</nav>

As we see, we have a fixed breadcrumb element, the Home page. Usually, it is the domain root, so we can’t generate it from the segments. But since we use collections for the breadcrumbs, we can check if the collection is empty, and we know we are in the root.

For every item, we can use its URL fragment, and by the url() helper, we can complete it.

On the other hand, since we are using a foreach loop, we have the $loop variable. It gives us a lot of flexibility. Here we use it for checking if the current item is the last one or not.

The interesting questions are the labels. Let’s start with the first one. If your app is in English only, you can easily just convert them to uppercase and that’s it. But what if you need localization or you just want to use something else than the capitalized version of the segment? You can use translations here:

Let’s say you have a breadcrumbs.php file for storing the formatted/translated version of the segments. In this case, you can do the following:

// for example: breadcrumbs.blog, breadcrumbs.category...

trans('breadcrumbs.'.$key);

Let’s talk about the last URL segment. What if my URL looks like this: blog/posts/41158 (sometimes this is the case)? Will we have the 41158 in the breadcrumb? What’s the point of that? Nothing. That’s why we used the @yield (‘title’) there.

Usually, we have a title section in our blade files, where we can extend the current title of the page. If we are talking about posts, usually the title section contains the title of the post. It’s kind because we have flexibility in general, but also here we can use it entirely. So in the link, we will see the ID or the slug of the model, but in the breadcrumb, we will see its title or a name. An example of defining the title section:

@section ('title', $post->title)

Summary

This approach can be a lifesaver for those, who need a straightforward yet flexible solution. Of course, this won’t work in every case. Maybe if you have very complex URL structure, you need a package to solve your needs.

But in general, it should be enough. Flexible, translatable and easily customizable with CSS. Or you can integrate with Bulma or Bootstrap as well. It’s up to you!