lafourchette/breadcrumbs-bundle
A Symfony bundle for building and rendering breadcrumb trails. Define breadcrumbs through service configuration or annotations, then display them in Twig templates with minimal setup for consistent navigation across your application.
First, the reality check: lafourchette/breadcrumbs-bundle is a Symfony bundle, not a Laravel package. There is no Illuminate\Support\ServiceProvider, no facade, no config/breadcrumbs.php, and no package auto-discovery. composer require lafourchette/breadcrumbs-bundle inside a Laravel app will install files that Laravel never boots. So "getting started" branches immediately:
LaFourchette\BreadcrumbsBundle\LaFourchetteBreadcrumbsBundle in config/bundles.php, then read DependencyInjection/Configuration.php and Resources/config/services.xml before the README. Those two files tell you the real service IDs, options, and defaults — the README is the most likely thing to be stale.Breadcrumbs singleton bound in a service provider plus a Blade component.Where to look first, either way:
First use case: a three-level trail — Home > Section > Current page — rendered from the layout so it appears on every page. Start with a hard-coded trail in one controller action to prove wiring end to end, then move definitions into whatever mechanism the bundle supports once rendering works. Do not start by making it dynamic across the whole site.
Signal to weigh: 0 dependents and no star count listed. That is an unmaintained-package profile. Budget for reading the source and possibly forking, not for composer update just working.
Build the trail in the controller or a dedicated builder — never in the template. Templates should receive a finished collection and do nothing but loop. The moment you start conditionally pushing items inside Twig/Blade you lose the ability to reuse the trail for JSON-LD, sitemaps, or tests.
Per-section builders for repeated trails. If /admin/users/* always sits under Admin > Users, define that once (a builder class, or a route-name → trail map in config) instead of repeating three addItem calls in fifteen actions. This is the single highest-leverage pattern and it is what makes breadcrumbs stop rotting as the app grows.
Render from the layout, override per page. Put the render call in the base layout so nothing is silently missing, and let individual actions replace the trail wholesale rather than appending to whatever the layout seeded.
Last item has no URL. The current page should render as plain text with aria-current="page", not a link to itself. Make the renderer treat null/empty URL as "current" so you cannot accidentally emit a self-link.
Translate at render time, not build time. Store labels as translation keys and resolve them in the template. If you resolve when pushing, a locale change mid-request (or a cached trail shared across locales) bakes in the wrong language.
Emit structured data from the same array. Loop the identical collection twice: once for HTML, once for a BreadcrumbList JSON-LD block. Markup and schema then cannot drift, which is the usual failure mode when SEO breadcrumbs are maintained by hand.
Laravel port shape:
// AppServiceProvider::register()
$this->app->scoped(Breadcrumbs::class, fn () => new Breadcrumbs());
// in a controller
breadcrumbs()->push(__('nav.home'), route('home'))
->push(__('nav.users'), route('users.index'))
->push($user->name); // current page: no URL
// resources/views/layouts/app.blade.php
<x-breadcrumbs :items="breadcrumbs()->all()" />
Use scoped (not singleton) — see the Octane gotcha below.
vendor:publish, no Blade directive, no config/ merge. Installing it in Laravel is dead weight unless you are explicitly porting it. Say this out loud in the PR description so the next developer does not spend an afternoon wondering why nothing renders.scoped in Laravel) or reset the trail at the start of each request.<a>-vs-<span> decision, separator handling, and escaping logic is where the bugs actually live. Copying only the item class gets you a data structure and a broken template.explode('/', $path) trails are neither.vendor/; but given the maintenance profile, an outright fork with your changes on main is the more honest move.How can I help you explore Laravel packages today?