Laravel Head: Gestión Avanzada de Meta Tags y JSON-LD
Laravel Head: Gestión Avanzada de Meta Tags y JSON-LD
Si desarrollas aplicaciones web modernas en Laravel, sabes que la gestión de meta tags, Open Graph y estructuras JSON-LD es crítica para SEO y compartir contenido en redes sociales. Hasta ahora, esto implicaba trabajar con componentes Blade dispersos o librerías de terceros. Con Laravel Head, anunciado por Taylor Otwell en Laracon US 2026, tenemos una solución first-party elegante y unificada.
En este artículo exploraremos cómo Laravel Head simplifica la gestión del documento <head> de tu aplicación, funciona perfectamente con Blade, Livewire e Inertia, y te permite escribir código más limpio y mantenible.
¿Qué es Laravel Head?
Laravel Head es un paquete oficial de Laravel que proporciona una API fluida para gestionar todos los aspectos del <head> de tu documento HTML. Permite definir títulos, meta tags, directivas robots, esquemas JSON-LD, hints de rendimiento y más, desde cualquier parte de tu aplicación.
La belleza de Laravel Head radica en su enfoque declarativo y centralizado: en lugar de esparcir meta tags en múltiples archivos Blade, defines todo en un solo lugar con una sintaxis intuitiva.
Comparación: antes y después
Antes (enfoque tradicional):
<!-- resources/views/layouts/app.blade.php -->
<head>
<title>{{ $title ?? 'Mi App' }}</title>
<meta name="description" content="{{ $description ?? '' }}">
<meta property="og:title" content="{{ $ogTitle ?? '' }}">
<meta property="og:description" content="{{ $ogDescription ?? '' }}">
<meta property="og:image" content="{{ $ogImage ?? '' }}">
<!-- Repetir esto en cada vista... -->
</head>
Con Laravel Head:
// En tu controlador
use Illuminate\Support\Facades\Head;
Head::title('Mi Artículo')
->description('Una descripción cautivadora')
->openGraph('og:title', 'Mi Artículo')
->openGraph('og:description', 'Una descripción cautivadora')
->openGraph('og:image', 'https://ejemplo.com/imagen.jpg');
Instalación y Configuración Básica
Instalar Laravel Head
composer require laravel/head
Publicar la configuración
php artisan vendor:publish --provider="Laravel\Head\HeadServiceProvider"
Registrar el componente Blade
En tu layout principal (resources/views/layouts/app.blade.php), incluye el componente Head:
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Aquí renderiza todo lo registrado en Laravel Head -->
<x-head />
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body>
{{ $slot }}
</body>
</html>
Uso Práctico: Gestión de Meta Tags
Títulos y Descripciones
La forma más básica de usar Laravel Head es establecer el título y descripción:
<?php
namespace App\Http\Controllers;
use Illuminate\Support\Facades\Head;
use App\Models\Post;
class PostController extends Controller
{
public function show(Post $post)
{
Head::title($post->title)
->description($post->excerpt);
return view('posts.show', ['post' => $post]);
}
}
Meta Tags Personalizados
Añade meta tags específicos para controlar el comportamiento de los navegadores y buscadores:
Head::title('Mis Productos')
->description('Catálogo completo de productos')
->tag('keywords', 'productos, tienda, ecommerce')
->tag('author', 'Mi Empresa')
->tag('robots', 'index, follow');
Open Graph para Redes Sociales
Define cómo se ve tu contenido cuando se comparte en Facebook, Twitter, LinkedIn, etc:
$post = Post::with('author')->find($id);
Head::title($post->title)
->description($post->excerpt)
->openGraph('og:title', $post->title)
->openGraph('og:description', $post->excerpt)
->openGraph('og:image', $post->featured_image_url)
->openGraph('og:url', route('posts.show', $post))
->openGraph('og:type', 'article')
->openGraph('article:published_time', $post->published_at->toAtomString())
->openGraph('article:author', $post->author->name);
Twitter Card
Personaliza cómo aparece tu contenido en Twitter:
Head::openGraph('twitter:card', 'summary_large_image')
->openGraph('twitter:creator', '@miusuario')
->openGraph('twitter:title', $post->title)
->openGraph('twitter:description', $post->excerpt)
->openGraph('twitter:image', $post->featured_image_url);
Esquemas JSON-LD para SEO Estructurado
JSON-LD es el formato recomendado por Google para datos estructurados. Laravel Head lo hace muy sencillo:
Artículo Blog
Head::jsonLd('Article', [
'headline' => $post->title,
'description' => $post->excerpt,
'image' => $post->featured_image_url,
'datePublished' => $post->published_at->toAtomString(),
'dateModified' => $post->updated_at->toAtomString(),
'author' => [
'@type' => 'Person',
'name' => $post->author->name,
],
]);
Producto (E-commerce)
Head::jsonLd('Product', [
'name' => $product->name,
'description' => $product->description,
'image' => $product->image_url,
'offers' => [
'@type' => 'Offer',
'url' => route('products.show', $product),
'priceCurrency' => 'EUR',
'price' => $product->price,
'availability' => $product->in_stock ? 'InStock' : 'OutOfStock',
],
'aggregateRating' => [
'@type' => 'AggregateRating',
'ratingValue' => $product->average_rating,
'reviewCount' => $product->reviews_count,
],
]);
Organización
Head::jsonLd('Organization', [
'name' => config('app.name'),
'url' => config('app.url'),
'logo' => asset('images/logo.png'),
'sameAs' => [
'https://twitter.com/miempresa',
'https://facebook.com/miempresa',
'https://linkedin.com/company/miempresa',
],
'contact' => [
'@type' => 'ContactPoint',
'contactType' => 'Customer Support',
'telephone' => '+34-123-456789',
'email' => 'soporte@miempresa.com',
],
]);
Integración con Livewire e Inertia.js
Usar Laravel Head en Componentes Livewire
En componentes Livewire, puedes actualizar los meta tags dinámicamente:
<?php
namespace App\Livewire;
use Livewire\Component;
use Illuminate\Support\Facades\Head;
use App\Models\Post;
class PostViewer extends Component
{
public $postId;
#[On('post-selected')]
public function updatePost($id)
{
$post = Post::find($id);
Head::title($post->title)
->description($post->excerpt)
->openGraph('og:title', $post->title)
->openGraph('og:image', $post->featured_image_url);
$this->postId = $id;
}
public function render()
{
return view('livewire.post-viewer', [
'post' => Post::find($this->postId),
]);
}
}
Usar Laravel Head con Inertia.js
En Inertia, pasa los datos de head a través de props y configúralos en el controlador:
<?php
namespace App\Http\Controllers;
use Inertia\Inertia;
use Illuminate\Support\Facades\Head;
use App\Models\Product;
class ProductController extends Controller
{
public function show(Product $product)
{
Head::title($product->name)
->description($product->short_description)
->openGraph('og:title', $product->name)
->openGraph('og:image', $product->image_url)
->jsonLd('Product', [
'name' => $product->name,
'image' => $product->image_url,
'offers' => [
'priceCurrency' => 'EUR',
'price' => $product->price,
],
]);
return Inertia::render('Product/Show', [
'product' => $product,
]);
}
}
Hints de Rendimiento
Laravel Head también permite agregar hints de rendimiento para optimizar la carga:
// Preconnect a servicios externos
Head::link('preconnect', 'https://fonts.googleapis.com')
->link('preconnect', 'https://cdn.example.com');
// Prefetch recursos que probablemente necesites
Head::link('prefetch', asset('js/heavy-component.js'));
// DNS prefetch para dominios externos
Head::link('dns-prefetch', '//api.ejemplo.com');
// Preload fuentes críticas
Head::link('preload', asset('fonts/inter-var.woff2'))
->attribute('as', 'font')
->attribute('type', 'font/woff2')
->attribute('crossorigin', true);
Directivas Robots
Controla cómo los motores de búsqueda indexan tu contenido:
// Contenido privado
Head::robots('noindex, nofollow');
// Contenido indexable pero sin seguimiento de links
Head::robots('index, nofollow');
// Permite indexación estándar
Head::robots('index, follow');
// Especificar máximo caché
Head::tag('robots', 'max-snippet:-1, max-image-preview:large, max-video-preview:-1');
Patrón Service: Centralizar la Lógica de Head
Para aplicaciones grandes, es recomendable crear un servicio que centralice la lógica de meta tags:
<?php
namespace App\Services;
use Illuminate\Support\Facades\Head;
use App\Models\Post;
class HeadService
{
public function setPostHead(Post $post): void
{
Head::title($post->title)
->description($post->excerpt)
->robots('index, follow')
->openGraph('og:title', $post->title)
->openGraph('og:description', $post->excerpt)
->openGraph('og:image', $post->featured_image_url)
->openGraph('og:type', 'article')
->openGraph('og:url', route('posts.show', $post))
->openGraph('article:published_time', $post->published_at->toAtomString())
->openGraph('article:author', $post->author->name)
->jsonLd('Article', [
'headline' => $post->title,
'description' => $post->excerpt,
'image' => $post->featured_image_url,
'datePublished' => $post->published_at->toAtomString(),
'author' => [
'@type' => 'Person',
'name' => $post->author->name,
],
]);
}
public function setProductHead($product): void
{
Head::title($product->name)
->description($product->short_description)
->openGraph('og:title', $product->name)
->openGraph('og:image', $product->image_url)
->jsonLd('Product', [
'name' => $product->name,
'image' => $product->image_url,
'offers' => [
'priceCurrency' => 'EUR',
'price' => $product->price,
'availability' => $product->in_stock ? 'InStock' : 'OutOfStock',
],
]);
}
}
Luego úsalo en tus controladores:
<?php
namespace App\Http\Controllers;
use App\Services\HeadService;
use App\Models\Post;
class PostController extends Controller
{
public function __construct(private HeadService $headService) {}
public function show(Post $post)
{
$this->headService->setPostHead($post);
return view('posts.show', ['post' => $post]);
}
}
Testing de Meta Tags
Verifica que tus meta tags se rendericen correctamente en tests:
<?php
namespace Tests\Feature;
use App\Models\Post;
use Illuminate\Support\Facades\Head;
use Tests\TestCase;
class PostMetaTagsTest extends TestCase
{
public function test_post_page_renders_correct_meta_tags()
{
$post = Post::factory()->create([
'title' => 'Mi Artículo Fantástico',
'excerpt' => 'Una descripción increíble',
]);
Head::title($post->title)
->description($post->excerpt)
->openGraph('og:title', $post->title);
$response = $this->get(route('posts.show', $post));
$response->assertSeeText('Mi Artículo Fantástico');
$response->assertSeeInOrder([
'name="description"',
'Una descripción increíble',
]);
}
}
Mejores Prácticas
1. Mantén Meta Tags Actualizados
Actualiza meta tags dinámicamente según el contenido:
// ✅ Bien
Head::title($dynamicTitle)
->description($dynamicDescription);
// ❌ Evita
Head::title('Título Estático');
2. Usa JSON-LD para SEO
Siempre proporciona datos estructurados para que Google entienda tu contenido:
// ✅ Incluye JSON-LD
Head::jsonLd('Article', ['headline' => $title]);
// ❌ Evita confiar solo en meta tags básicos
3. Optimiza Imágenes para Open Graph
Las imágenes deben ser de al menos 1200x630px:
Head::openGraph('og:image', $post->getOgImageUrl()); // Imagen optimizada
4. Centraliza la Lógica en Servicios
No desperdigues la lógica de head por múltiples controladores:
// ✅