En este contenido te explicaré por qué los slugs son tan importantes para el SEO y cómo añadirlos en tus modelos en Laravel puede mejorar la comprensión del contenido de tu página por parte de los motores de búsqueda.
Los slugs son importantes para el SEO porque ayudan a los motores de búsqueda a comprender el contenido de una página de manera más clara y efectiva. Los slugs son una versión simplificada y legible por humanos de la URL de una página, que generalmente incluye el título del artículo o del contenido.
Por ejemplo, si tenemos una publicación titulada “Laravel 10 y sus novedades” entonces nuestro slug sería “laravel-10-y-sus-novedades”.
Crearemos un modelo y su respectiva migración (-m) de la tabla posts en el terminal.
1php artisan make:model Post -m
Nuestra migración tendrá las siguientes columnas.
1public function up(): void 2{ 3 Schema::create('posts', function (Blueprint $table) { 4 $table->id(); 5 6 $table->string('title'); 7 $table->text('description'); 8 $table->text('content'); 9 $table->string('image_path');10 $table->string('slug');11 $table->timestamp('published_at');12 13 $table->timestamps();14 });15}
y lo ejecutamos con el siguiente comando.
1php artisan migrate
Nuestra BD se vería de la siguiente manera.
Ahora agregaremos una ruta en el archivo routes/web.php que retornará la vista donde estarán la lista de todos los posts.
1use App\Models\Post;2 3Route::get('posts', function () {4 $posts = Post::select(['id', 'title', 'image_path', 'description', 'slug', 'published_at'])5 ->orderByDesc('published_at')6 ->get();7 8 return view('posts', compact('posts'));9})->name('show-posts');
La vista la crearemos en resources/views/posts.blade.php y tendrá el siguiente código HTML con estilos Tailwind que hemos extraído de Tailwind Components.
1<!DOCTYPE html> 2<html lang="{{ str_replace('_', '-', app()->getLocale()) }}"> 3 <head> 4 <meta charset="utf-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1"> 6 7 <title>Laravel</title> 8 9 <!-- Fonts -->10 <link rel="preconnect" href="https://fonts.bunny.net">11 <link href="https://fonts.bunny.net/css?family=figtree:400,600&display=swap" rel="stylesheet" />12 13 <script src="https://cdn.tailwindcss.com"></script>14 </head>15 <body class="antialiased">16 <!-- component -->17 <section class="bg-white dark:bg-gray-900">18 <div class="container px-6 py-10 mx-auto">19 <h1 class="text-3xl font-semibold text-gray-800 capitalize lg:text-4xl dark:text-white">From the blog</h1>20 21 <div class="grid grid-cols-1 gap-8 mt-8 md:mt-16 md:grid-cols-2">22 @foreach($posts as $post)23 <div class="lg:flex">24 <img class="object-cover w-full h-56 rounded-lg lg:w-64" src="{{ $post->image_path }}" alt="">25 26 <div class="flex flex-col justify-between py-6 lg:mx-6">27 <a href="/posts/{{ $post->slug }}" class="text-xl font-semibold text-gray-800 hover:underline dark:text-white ">28 {{ $post->title }}29 </a>30 31 <span class="text-sm text-gray-500 dark:text-gray-300">{{ $post->published_at }}</span>32 </div>33 </div>34 @endforeach35 </div>36 </div>37 </section>38 </body>39</html>
Dado que no tenemos ningún post creado, la vista no nos mostrará nada así que crearemos un factory para crear varios posts automáticamente.
1php artisan make:factory PostFactory
En nuestro archivo database/factory/PostFactory.php agregamos las columnas con los datos de prueba que usaremos.
1<?php 2 3namespace Database\Factories; 4 5use Illuminate\Database\Eloquent\Factories\Factory; 6 7/** 8 * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Post> 9 */10class PostFactory extends Factory11{12 /**13 * Define the model's default state.14 *15 * @return array<string, mixed>16 */17 public function definition(): array18 {19 return [20 'title' => fake()->text(40),21 'description' => fake()->text(100),22 'content' => fake()->text(300),23 'image_path' => fake()->imageUrl,24 'slug' => null,25 'published_at' => fake()->dateTimeThisYear26 ];27 }28}
Lo que vamos hacer es que cada vez que se guarde un post, el slug se cree automáticamente en base al nombre, para ello tenemos que crear un mutator en nuestro archivo Models/Post.php.
1<?php 2 3namespace App\Models; 4 5use Illuminate\Database\Eloquent\Casts\Attribute; 6use Illuminate\Database\Eloquent\Factories\HasFactory; 7use Illuminate\Database\Eloquent\Model; 8use Illuminate\Support\Str; 9 10class Post extends Model11{12 use HasFactory;13 14 protected function slug(): Attribute15 {16 return Attribute::make(17 get: fn ($value) => $value,18 set: fn ($value) => Str::slug($this->title)19 );20 }21}
Para poder probar vamos a crear nuestros posts con el factory, para ello usaremos Laravel Tinker
1php artisan tinker
Dentro generaremos 40 posts automáticamente con este código:
1App\Models\Post::factory()->count(40)->create()
Si entramos a nuestra vista “/posts” podemos ver la lista de los posts que hemos creado.
Además veremos que los registros de nuestra tabla posts incluye la columna slug que se ha creado automáticamente gracias a nuestro mutator.
Lo que haremos ahora es que al dar clic a un post nos envié al contenido. Para ello agregamos otra ruta en el archivo routes/web.php.
1Route::get('posts/{post:slug}', function (Post $post) {2 return view('post', compact('post'));3})->name('show-post');
Cabe destacar que estamos usando el Route Model Binding para que podamos obtener el post pasándole el slug.
La vista la obtendremos de Tailwind Toolbox y la pondremos en el archivo resources/views/post.blade.php
1<!DOCTYPE html> 2<html lang="{{ str_replace('_', '-', app()->getLocale()) }}"> 3 <head> 4 <meta charset="utf-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1"> 6 7 <title>Laravel</title> 8 9 <!-- Fonts -->10 <link rel="preconnect" href="https://fonts.bunny.net">11 <link href="https://fonts.bunny.net/css?family=figtree:400,600&display=swap" rel="stylesheet" />12 13 <script src="https://cdn.tailwindcss.com"></script>14 </head>15 <body class="antialiased">16 <div class="container w-full md:max-w-3xl mx-auto pt-20">17 18 <div class="w-full px-4 md:px-6 text-xl text-gray-800 leading-normal" style="font-family:Georgia,serif;">19 20 <!--Title-->21 <div class="font-sans">22 <p class="text-base md:text-sm text-green-500 font-bold">< <a href="/posts" class="text-base md:text-sm text-green-500 font-bold no-underline hover:underline">BACK TO BLOG</a></p>23 <h1 class="font-bold font-sans break-normal text-gray-900 pt-6 pb-2 text-3xl md:text-4xl">{{ $post->title }}</h1>24 <p class="text-sm md:text-base font-normal text-gray-600">Published {{ $post->published_at }}</p>25 </div>26 27 28 {{ $post->content }}29 30 </div>31 32 <!--Tags -->33 <div class="text-base md:text-sm text-gray-500 px-4 py-6">34 Tags: <a href="#" class="text-base md:text-sm text-green-500 no-underline hover:underline">Link</a> . <a href="#" class="text-base md:text-sm text-green-500 no-underline hover:underline">Link</a>35 </div>36 37 <!--Divider-->38 <hr class="border-b-2 border-gray-400 mb-8 mx-4">39 40 41 <!--Subscribe-->42 <div class="container px-4">43 <div class="font-sans bg-gradient-to-b from-green-100 to-gray-100 rounded-lg shadow-xl p-4 text-center">44 <h2 class="font-bold break-normal text-xl md:text-3xl">Subscribe to my Newsletter</h2>45 <h3 class="font-bold break-normal text-gray-600 text-sm md:text-base">Get the latest posts delivered right to your inbox</h3>46 <div class="w-full text-center pt-4">47 <form action="#">48 <div class="max-w-xl mx-auto p-1 pr-0 flex flex-wrap items-center">49 <input type="email" placeholder="youremail@example.com" class="flex-1 mt-4 appearance-none border border-gray-400 rounded shadow-md p-3 text-gray-600 mr-2 focus:outline-none">50 <button type="submit" class="flex-1 mt-4 block md:inline-block appearance-none bg-green-500 text-white text-base font-semibold tracking-wider uppercase py-4 rounded shadow hover:bg-green-400">Subscribe</button>51 </div>52 </form>53 </div>54 </div>55 </div>56 <!-- /Subscribe-->57 58 59 60 <!--Author-->61 <div class="flex w-full items-center font-sans px-4 py-12">62 <img class="w-10 h-10 rounded-full mr-4" src="http://i.pravatar.cc/300" alt="Avatar of Author">63 <div class="flex-1 px-2">64 <p class="text-base font-bold text-base md:text-xl leading-none mb-2">Jo Bloggerson</p>65 <p class="text-gray-600 text-xs md:text-base">Minimal Blog Tailwind CSS template by <a class="text-green-500 no-underline hover:underline" href="https://www.tailwindtoolbox.com">TailwindToolbox.com</a></p>66 </div>67 <div class="justify-end">68 <button class="bg-transparent border border-gray-500 hover:border-green-500 text-xs text-gray-500 hover:text-green-500 font-bold py-2 px-4 rounded-full">Read More</button>69 </div>70 </div>71 <!--/Author-->72 73 <!--Divider-->74 <hr class="border-b-2 border-gray-400 mb-8 mx-4">75 76 <!--Next & Prev Links-->77 <div class="font-sans flex justify-between content-center px-4 pb-12">78 <div class="text-left">79 <span class="text-xs md:text-sm font-normal text-gray-600">< Previous Post</span><br>80 <p><a href="#" class="break-normal text-base md:text-sm text-green-500 font-bold no-underline hover:underline">Blog title</a></p>81 </div>82 <div class="text-right">83 <span class="text-xs md:text-sm font-normal text-gray-600">Next Post ></span><br>84 <p><a href="#" class="break-normal text-base md:text-sm text-green-500 font-bold no-underline hover:underline">Blog title</a></p>85 </div>86 </div>87 88 89 <!--/Next & Prev Links-->90 91 </div>92 <!--/container-->93 </body>94</html>
¡Y listo! ya podemos navegar a nuestros posts usando el slug.
Tendrás tutoriales, tips, conceptos y puedas convertirte en un artesano de todo el ecosistema Laravel.
Revisa los detalles del nuevo curso en desarrollo