SEO técnico en Laravel 13: meta tags, canonical, sitemap y datos estructurados
En WordPress el SEO llega con un plugin; en Laravel 13 se construye con código, y esa es la ventaja. El SEO técnico en Laravel 13 cubre meta tags, canonical, sitemap, robots.txt y datos estructurados desde Blade y paquetes como spatie, con control total sobre cada etiqueta que ve Google.
Por qué el SEO técnico en Laravel es diferente
WordPress resuelve el SEO con plugins; Laravel exige código
Un plugin como Yoast o RankMath escribe las meta tags, genera el sitemap y gestiona las redirecciones en WordPress. En Laravel no existe ese atajo: cada pieza se implementa a nivel de código. Lo que parece una desventaja es en realidad el mayor beneficio: cero plugins de terceros que ralenticen la aplicación, etiquetas exactamente como las necesitas y una base de código que entiendes por completo. Los puntos de fallo típicos detectados en auditorías de apps Laravel son justamente los que el código resuelve: route bindings sin canonical, definiciones canonical ausentes y consultas sin optimizar que lastran la visibilidad.
Lo mínimo que toda app Laravel necesita para indexarse bien
Para que Google entienda tu aplicación necesitas cinco piezas: un título y una descripción únicos por página, una etiqueta canonical que señale la URL original, un sitemap XML que liste el contenido indexable, un robots.txt con las reglas de rastreo y datos estructurados cuando el contenido lo merezca. En un blog bilingüe como blenderdeluxe se suma el hreflang, para que las versiones ES y EN no compitan entre sí.
Meta tags y Open Graph desde Blade
El layout como base: title, description y canonical por página
El layout de tu aplicación, normalmente un componente Blade como x-app-layout, es el lugar natural para el bloque de meta. Declara un stack y deja que cada vista inyecte sus valores:
Lee también
<head>
<title>@yield('title', config('app.name'))</title>
@stack('meta')
</head>En cada vista, con @push rellenas los valores específicos de esa página:
@push('meta')
<meta name="description" content="{{ $post->excerpt }}">
<link rel="canonical" href="{{ url()->current() }}">
@endpushEste patrón aprovecha los componentes Blade que ya usas en Laravel 13, y mantiene el SEO de cada página en un solo sitio, sin duplicar lógica entre vistas.
Open Graph y Twitter Cards para compartir en redes
Cuando alguien comparte tu URL en redes, el enlace se renderiza con las etiquetas Open Graph. Añade og:title, og:description, og:type y og:image en el mismo stack, junto a las Twitter Cards equivalentes. La imagen social merece atención: es el elemento que más decide si un enlace se clica, y en Laravel 13 puedes generar el build de assets con Vite, como vimos en nuestro artículo de Laravel 13 + Vite.
Canonical: la URL que manda
Por qué el contenido duplicado se come el ranking
Google penaliza la duplicidad: si la misma página responde en varias URLs, el buscador reparte la autoridad entre todas y ninguna rankea bien. En Laravel la duplicidad aparece sola: parámetros de consulta, paginación, variantes de orden o el mismo contenido accesible por slug y por id. La etiqueta canonical indica a Google qué URL contiene el contenido original.
Canonical dinámico con slugs, paginación y parámetros de consulta
La solución es generar el canonical dinámico en cada página:
<link rel="canonical" href="{{ url()->current() }}">Para un post, la URL canónica es la del slug, sin parámetros de seguimiento ni variantes de idioma. En listados paginados, cada página de resultados declara su propia canonical a sí misma, salvo la primera, que puede apuntar a la raíz del listado. La regla de oro: una página, una canonical, y que apunte siempre a la versión que quieres que rankee.
Sitemap XML dinámico con spatie/laravel-sitemap
Rutas estáticas y modelos en el mismo sitemap
El paquete spatie/laravel-sitemap genera sitemaps de forma programática combinando rutas estáticas y modelos. En un blog, quieres las páginas principales más todos los posts publicados:
use Spatie\Sitemap\Sitemap;
Sitemap::create()
->add('/')
->add('/about')
->add(Post::published()->get())
->writeToFile(public_path('sitemap.xml'));El paquete itera el modelo, usa su ruta y respeta el campo updated_at para el lastmod. Lo conectas a una tarea programada para regenerar el sitemap cuando cambia el contenido.
Sitemap index, lastmod y cacheo del XML
Cuando el sitio crece, usa un sitemap index que agrupe varios archivos: uno para posts, otro para categorías, otro para páginas estáticas. spatie/laravel-sitemap soporta este formato con SitemapIndex. El lastmod debe reflejar la última modificación real, y el XML conviene cachearlo o generarlo bajo demanda con una ruta dedicada, para no reconstruir todo el sitemap en cada petición.
robots.txt y X-Robots-Tag con spatie/laravel-robots-middleware
Noindex para admin, borradores y entornos de staging
spatie/laravel-robots-middleware decide por página si se indexa o no, combinando robots.txt, meta robots y cabeceras X-Robots-Tag. Por defecto permite la indexación en todas las rutas; hereda una clase propia y define las reglas de tu aplicación:
class RobotsMiddleware extends RobotsMiddleware
{
protected function shouldIndex(Request $request): bool
{
return $request->is('admin/*') || $request->is('drafts/*')
? false
: parent::shouldIndex($request);
}
}Así, el panel de administración y los borradores emiten noindex automáticamente, y el robots.txt resultante refleja las mismas reglas. Es la diferencia entre un staging que Google ignora y un staging que se indexa sin querer.
Datos estructurados con JSON-LD
Article y BlogPosting para un blog
Los datos estructurados habilitan los rich results: estrellas, migas de pan o el autor en la SERP. Para un blog, el tipo BlogPosting (subclase de Article) comunica a Google el título, la fecha, el autor y el editor:
use Spatie\SchemaOrg\Schema;
$jsonLd = Schema::blogPosting()
->headline($post->title)
->datePublished($post->published_at->toIso8601String())
->author(Schema::person()->name('Diego Cortés'))
->publisher(Schema::organization()->name('Blender Deluxe'))
->toScript();spatie/schema-org es un builder tipado y fluido para los principales tipos de Schema.org, y se integra con el mismo flujo de @stack que las meta tags.
FAQPage y BreadcrumbList con spatie/schema-org
Si el post responde preguntas concretas, el JSON-LD de tipo FAQPage puede generar el desplegable de preguntas en Google. Las migas de pan, con BreadcrumbList, mejoran la comprensión de la jerarquía del sitio. Ambos se añaden igual que BlogPosting, y conviene no abusar: Google solo muestra rich results cuando el contenido realmente los justifica.
Validar con el Rich Results Test de Google
Antes de desplegar, pega la URL en el Rich Results Test de Google o valida el JSON-LD con el validador de Schema.org. Un error de sintaxis en el JSON no rompe la página, pero sí elimina el rich result silenciosamente. Incluye esta validación en tu checklist de release.
Hreflang: SEO multilingüe en un blog ES/EN
Canonical por idioma y sitemap por idioma
En un blog bilingüe, cada página debe declarar sus alternativas de idioma. En la vista del post añades los enlaces hreflang:
<link rel="alternate" hreflang="es" href="{{ $post->urlEs }}">
<link rel="alternate" hreflang="en" href="{{ $post->urlEn }}">
<link rel="alternate" hreflang="x-default" href="{{ $post->urlEs }}">Combinado con un canonical por idioma y un sitemap que incluya ambas versiones, el buscador entiende que es el mismo contenido en dos idiomas. Sin hreflang, las dos versiones compiten por las mismas búsquedas y ambas rankean peor que cualquiera de las dos por separado.
x-default y los errores típicos de hreflang
El valor x-default señala la versión por defecto para usuarios sin idioma preferente. Los errores clásicos: declarar hreflang a una URL que redirige, olvidar el canonical por idioma, o declarar hreflang en una dirección y no en la contraria. La regla es simple: si la página A apunta a B, B debe apuntar a A, y ambas deben ser accesibles sin redirección.
Redirecciones 301 y URLs limpias
Route::redirect y cambios de slug sin perder autoridad
Cuando cambias un slug o mueves una sección, una redirección 301 traslada la autoridad de la URL antigua a la nueva. Laravel lo resuelve en una línea:
Route::redirect('/blog/viejo-slug', '/blog/nuevo-slug', 301);Lo mismo sirve para migrar dominios o unificar variantes con y sin www. Mantén un registro de redirecciones para no acumular cadenas, y evita redirigir a la home: cada 301 debe aterrizar en la página equivalente, o perderás el contexto de la consulta original.
Checklist de SEO técnico para tu release
Antes de publicar una app Laravel 13, repasa esta lista: title y description únicos por página, canonical dinámico sin parámetros, sitemap regenerado y cacheado, robots.txt correcto, noindex en admin y borradores, JSON-LD validado, hreflang bidireccional en sitios multilingües y redirecciones 301 para URLs antiguas. Con esto cubierto, la indexación deja de ser un misterio y se convierte en una propiedad más de tu código.
Conclusión
El SEO técnico en Laravel 13 no requiere plugins: requiere un layout bien organizado, un par de paquetes de spatie y disciplina para mantener canonical, sitemap y robots en orden. El resultado es un control total que ningún CMS te da. Sigue leyendo el blog para más guías de Laravel 13, desde componentes Blade hasta colas y jobs en producción.


