Antes que te vayas
Inscríbete en nuestro curso gratuito de Laravel
En esta ocasión vamos a ver cómo generar un sitemap para nuestros proyectos Laravel.
Pero empecemos respondiendo algunas preguntas frecuentes.
Un sitemap es un archivo XML (o bien una ruta que devuelve una respuesta en formato XML), indicando las distintas páginas que están presentes en nuestro sitio web.
No todas, pero sí las más importantes: aquellas que queremos que sean indexadas por los buscadores (como Google).
¿Por qué es importante?
Muy buena pregunta.
Si no tienes un sitemap, Google puede indexar tu sitio web de todas formas, pero, puede tardar más tiempo o bien puede no indexar todas las secciones de tu sitio web.
Los sitemaps son importantes para mejorar el SEO de tu página. SEO significa "Search Engine Optimization", y hace referencia a qué tan bien posicionado se encuentra tu sitio web en los motores de búsqueda.
De hecho, si buscas analizadores de SEO (existen varios gratuitos), verás que la gran mayoría (si no es que todos), consideran como un factor importante contar con un sitemap. Si no tienes uno, te consideran unos puntos menos en su evaluación.
Ten en cuenta que:
Un sitemap es la solución ideal ante dicho escenario.
Como debes haber notado, hoy en día existen muchos paquetes para Laravel.
Sin embargo, en esta ocasión vamos a escribir nuestra propia solución. De paso que conocemos más acerca de la estructura de un sitemap.
Esto depende de las características de tu proyecto.
Por ejemplo:
Lo que no debemos incluir en un sitemap es más bien:
Rutas que ejecutan acciones. Por ejemplo: si tenemos rutas que guardan preferencias, como agregar a favoritos, que activan un "modo nocturno" en nuestra página, o que no devuelven una vista, no tiene sentido considerarlas como parte de nuestro sitemap.
Si estás usando los verbos POST, PUT, PATCH y DELETE como es debido, te resultará más sencillo excluir estas rutas.
Un sitemap se define en formato XML y aquí podemos ver un ejemplo usando los datos que típicamente se indican por cada URL:
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://programacionymas.com/</loc>
<lastmod>2019-07-31</lastmod>
<changefreq>monthly</changefreq>
<priority>1.0</priority>
</url>
</urlset>
En el ejemplo anterior tenemos una única etiqueta url
al interior del urlset
. La idea es tener una por cada página pública de nuestro sitio.
Seguro que ya imaginas el significado de cada dato en su interior:
loc
: Es la URL de la página.lastmod
: Fecha de su última modificación.changefreq
: Frecuencia con que se actualiza.priority
: La importancia de esta página.Respecto a la prioridad, debe asignarse 1.0
a la página más importante de nuestro sitio (generalmente la página de inicio).
La prioridad para las demás páginas será menor, según su importancia, en este sentido: 0.9
, 0.8
, 0.7
, 0.6
, 0.5
(los valores válidos se expresan en décimas).
Sobre la frecuencia de cambio, tenemos varios valores disponibles. Los menciono aquí para que los tengas a tu alcance:
En proyectos medianamente grandes, existe una tendencia a organizar nuestras rutas en distintos archivos. Entonces mi sugerencia es:
¿A qué me refiero con esto último?
/planes
que lista los planes de suscripción disponibles./blog/{article}
representa a cada uno de los artículos de nuestro blog.Una vez hecho ello, lo siguiente es:
A veces encontramos rutas que redirigen hacia otras, porque en algún momento decidimos cambiarlas.
¿Cuál de las rutas agregamos al sitemap? ¿La antigua, la ruta nueva o ambas?
Para tomar una decisión necesitamos saber si aún recibimos visitas en las rutas antiguas.
Si dichas rutas ya no son visitadas, podemos reciclarlas.
¿Pero cómo sabemos si una página en específico de nuestro sitio recibe visitas o no?
Para ello podemos apoyarnos de Google Analytics (si no lo usas, te recomiendo empezar a hacerlo, para que tengas estadísticas de qué secciones son las más visitadas).
Los pasos a seguir son 2:
Un subdominio resulta muy útil para separar contenido: porque la temática es distinta, o porque se trata de una variante que necesita su propio espacio.
Por ejemplo, Google usa subdominios distintos para sus productos:
news.google.com
: Google Noticiasmaps.google.com
: Google Mapsplay.google.com
: Google Play StoreEntonces: lo recomendable es tener un sitemap por cada subdominio.
Si un subdominio es dado de baja, su sitemap correspondiente desaparecerá, pero esto no afectará al sitemap de los demás subdominios (ni al sitemap del dominio principal).
A estas alturas debes tener identificadas las rutas que quieres considerar en el sitemap de tu sitio web.
A continuación te presento un ejemplo simplificado, con relación a este mismo sitio web, sobre el cual navegas.
Dominio principal
Ruta | Descripción | Tipo de ruta | Prioridad |
---|---|---|---|
/ | Página de inicio. | Única | 1.0 |
/asesoria | Información acerca de sesiones de asesoría. | Única | 0.8 |
/contacto | Formulario de contacto. | Única | 0.8 |
/becas | Información sobre becas y descuentos. | Única | 0.7 |
/@{username} | Páginas de perfil para cada usuario. | Múltiple | 0.7 |
/blog | Últimos artículos. Categorías y etiquetas. | Única | 0.8 |
/blog/{slug} | Representa a cada artículo del blog. | Múltiple | 0.9 |
/categorias | Todas las categorías del blog. | Única | 0.7 |
/categorias/{slug} | Lista de artículos para una categoría. | Múltiple | 0.8 |
/tags | Todas las etiquetas del blog. | Única | 0.7 |
/tags/{name} | Artículos asociados con la etiqueta seleccionada. | Múltiple | 0.8 |
/portafolio | Listado de algunas aplicaciones desarrolladas. | Única | 0.6 |
/portafolio/{slug} | Información detallada sobre una aplicación. | Múltiple | 0.6 |
/{slug} | Páginas informativas, como guías o anuncios. | Múltiple | 0.8 |
Subdominio "series"
Ruta | Descripción | Tipo de ruta | Prioridad |
---|---|---|---|
/ | Página principal. | Única | 1.0 |
/categorias | Listado de categorías (o tecnologías). | Única | 0.8 |
/categorias/{slug} | Lista de series pertenecientes a una categoría. | Múltiple | 0.8 |
/lecciones/{episode} | Lección independiente. | Múltiple | 0.7 |
/{slug} | Página de serie: capítulos e información. | Múltiple | 0.9 |
/{slug}/{episode} | Un episodio (o capítulo) perteneciente a una serie. | Múltiple | 0.8 |
Bien.
Llegados a este punto lo primero que haremos es definir una ruta para mostrar allí nuestro sitemap:
Route::get('/sitemap.xml', '[email protected]');
Ten en cuenta que, aunque la ruta termina en .xml
es una ruta declarada como cualquier otra, y no un archivo.
La ruta puede llamarse de manera diferente si lo prefieres, por ejemplo /sitemap
, pero si ese es el caso, deberás indicar ello en el archivo robots.txt para que sea accesible por los motores de búsqueda (y sean conscientes de ello).
Entonces, vamos a empezar creando un controlador nuevo:
php artisan make:controller SiteMapController
Y definiendo un método index
en su interior:
class SiteMapController extends Controller
{
private $siteMap;
public function index()
{
$this->siteMap = new SiteMap();
$this->addUniqueRoutes();
$this->addArticles();
$this->addCategories();
$this->addDynamicPages();
$this->addTags();
$this->addProjects();
$this->addProfilePages();
return response($this->siteMap->build(), 200)
->header('Content-Type', 'text/xml');
}
private function addUniqueRoutes()
{
// ...
}
private function addProfilePages()
{
// ...
}
private function addArticles()
{
// ...
}
private function addCategories()
{
// ...
}
private function addTags()
{
// ...
}
private function addProjects()
{
// ...
}
private function addDynamicPages()
{
// ...
}
}
$siteMap
para que sea accesible en toda la clase.index
creamos una instancia de la clase Sitemap
He creído conveniente usar 2 clases: una para representar al sitemap de nuestro sitio, y otra para representar a cada URL en su interior.
Clase Sitemap
class SiteMap
{
const START_TAG = '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">';
const END_TAG = '</urlset>';
// to build the XML content
private $content;
public function add(Url $siteMapUrl)
{
$this->content .= $siteMapUrl->build();
}
public function build()
{
return self::START_TAG . $this->content . self::END_TAG;
}
}
Clase URL
class Url
{
private $url;
private $lastUpdate;
private $frequency;
private $priority;
public static function create($url)
{
$newNode = new self();
$newNode->url = url($url);
return $newNode;
}
public function lastUpdate($lastUpdate)
{
$this->lastUpdate = $lastUpdate;
return $this;
}
public function frequency($frequency)
{
$this->frequency = $frequency;
return $this;
}
public function priority($priority)
{
$this->priority = $priority;
return $this;
}
public function build()
{
// $url = 'https://programacionymas.com/';
// $lastUpdate = '2019-07-31T01:06:39+00:00';
// $frequency = 'monthly';
// $priority = '1.00';
return "<url>" .
"<loc>$this->url</loc>" .
"<lastmod>$this->lastUpdate</lastmod>" .
"<changefreq>$this->frequency</changefreq>" .
"<priority>$this->priority</priority>" .
"</url>";
}
}
¿Dónde debes situar estas clases?
Eso depende de tu proyecto. Por ejemplo:
Ahora bien, ¿qué representan los métodos privados del ejemplo?
addUniqueRoutes
es un método para agregar las URLs de las rutas únicas al sitemap.addArticles
, addCategories
, addDynamicPages
y todos los demás son métodos para agregar contenido dinámico al sitemap.Empecemos definiendo las "rutas únicas", que son constantes (son fijas y no requieren consultar nuestra base de datos):
private function addUniqueRoutes()
{
$startOfMonth = Carbon::now()->startOfMonth()->format('c');
$this->siteMap->add(
Url::create('/')
->lastUpdate($startOfMonth)
->frequency('monthly')
->priority('1.00')
);
$this->siteMap->add(
Url::create('/asesoria')
->lastUpdate($startOfMonth)
->frequency('monthly')
->priority('0.8')
);
$this->siteMap->add(
Url::create('/contacto')
->lastUpdate($startOfMonth)
->frequency('yearly')
->priority('0.8')
);
$this->siteMap->add(
Url::create('/becas')
->lastUpdate($startOfMonth)
->frequency('monthly')
->priority('0.7')
);
$this->siteMap->add(
Url::create('/blog')
->lastUpdate($startOfMonth)
->frequency('monthly')
->priority('0.8')
);
$this->siteMap->add(
Url::create('/categorias')
->lastUpdate($startOfMonth)
->frequency('yearly')
->priority('0.7')
);
$this->siteMap->add(
Url::create('/tags')
->lastUpdate($startOfMonth)
->frequency('yearly')
->priority('0.7')
);
$this->siteMap->add(
Url::create('/portafolio')
->lastUpdate($startOfMonth)
->frequency('yearly')
->priority('0.6')
);
}
En el ejemplo anterior debes modificar las URLs, la frecuencia de actualización, la prioridad, y agregar o quitar URLs según corresponda.
La variable $startOfMonth
es una cadena que representa una fecha con el formato requerido por el sitemap.
La fecha representada es el inicio del mes actual. Puedes usar lo mismo o modificar la fecha si conoces cuándo se modificaron por última vez tales rutas.
Respecto a las rutas que representan múltiples URLs, éstas generalmente contienen parámetros de ruta, por lo que debemos iterar sobre nuestros datos, y agregar cada una de tales entidades al sitemap, con el formato adecuado.
La implementación de esto depende de las entidades (o modelos) que tengas definidos en tu proyecto.
De todas formas, a modo de ejemplo, te muestro a continuación cómo agrego los artículos de mi blog al sitemap, y así mismo las categorías, y las páginas que están definidas dinámicamente (a través de un editor y almacenadas en la base de datos):
private function addArticles()
{
$articles = Article::published()->whereNotNull('slug')->get([
'slug', 'updated_at'
]);
foreach ($articles as $article) {
$this->siteMap->add(
Url::create("/blog/$article->slug")
->lastUpdate($article->updated_at->startOfMonth()->format('c'))
->frequency('monthly')
->priority('0.9')
);
}
}
private function addCategories()
{
$categories = ArticleCategory::withCount('articles')
->having('articles_count', '>', 0)
->get(['slug', 'updated_at']);
foreach ($categories as $category) {
$this->siteMap->add(
Url::create("/categorias/$category->slug")
->lastUpdate($category->updated_at->startOfMonth()->format('c'))
->frequency('monthly')
->priority('0.8')
);
}
}
private function addDynamicPages()
{
$pages = Page::where('published', true)->get(['slug', 'updated_at']);
foreach ($pages as $page) {
$this->siteMap->add(
Url::create($page->slug)
->lastUpdate($page->updated_at->startOfMonth()->format('c'))
->frequency('monthly')
->priority('0.8')
);
}
}
Si eres observador, habrás notado que en el ejemplo anterior:
updated_at
para obtener desde allí una cadena, con el formato requerido para la fecha.Si tienes múltiples entidades y quieres evitar que todas tus consultas se ejecuten continuamente, puedes hacer uso de la clase Cache
de Laravel.
Esta clase permite recordar valores temporalmente.
Como en nuestra solución, no usamos ninguna vista blade y simplemente creamos una cadena con el contenido adecuado, podemos recordar este valor usando Cache
.
¿Cómo se hace ello?
Podemos actualizar nuestro método index
de esta manera:
public function index()
{
$siteMapXml = Cache::remember('sitemap', 3, function () {
$this->siteMap = new SiteMap();
$this->addUniqueRoutes();
$this->addArticles();
$this->addCategories();
$this->addDynamicPages();
$this->addTags();
$this->addProjects();
$this->addProfilePages();
return $this->siteMap->build();
});
return response($siteMapXml, 200)
->header('Content-Type', 'text/xml');
}
El método remember
recordará el contenido de nuestro sitemap bajo el nombre "sitemap" durante el tiempo que le indiquemos en el segundo parámetro.
Y cuando la variable caduque y no exista (o bien se acceda por 1ra vez), la función que está en el 3er parámetro se ejecutará, obteniendo un nuevo valor para ser recordado.
Sólo ten cuidado con el 2do parámetro. Por ejemplo:
Como ves, no es complicado definir un sitemap, pero sí requiere algo de tiempo revisar todas las rutas que tenemos en nuestro proyecto.
Lo bueno de esto es que, podemos aprovechar la ocasión para identificar:
Y bien:
Espero que estos ejemplos te hayan sido de ayuda y puedas configurar adecuadamente un sitemap para cada uno de tus proyectos Laravel :)
Aprende Laravel 5.5 desde cero y desarrolla aplicaciones web reales, en tiempo récord, de la mano de Laravel.
Ver másVeamos cómo implementar un login mediante redes sociales! Aprende y aplica esto sobre cualquier proyecto Laravel.
Ver másDesarrollemos un Messenger! Aprende sobre Channels, Queues, Vuex, JWT, Sesiones, BootstrapVue y mucho más.
Ver másInscríbete en nuestro curso gratuito de Laravel