Complemento del blog · Relacionados

Los Relacionados: «sigue leyendo» que encadena el blog

El bloque que cierra cada artículo con un puñado de enlaces a otras entradas. No es un adorno al pie: es lo que evita el callejón sin salida al final de la lectura y, de paso, reparte autoridad interna entre los artículos del blog.

Esta página no es una ficha técnica: es el complemento entero, abierto y explicado. Qué problema resuelve y por qué cierra el artículo, de qué piezas se compone, cómo se comporta en el teléfono, dónde encaja y, al final, cómo está construido —del criterio de diseño a la línea de código—.

Con un matiz que conviene marcar desde el inicio: hoy los candidatos son hermanos de la colección —los demás artículos, sin más—. Funciona y nunca se queda sin opciones, pero el patrón ideal es priorizar por misma categoría o tags compartidos. Esa mejora se documenta aquí, con su receta.

Definición

¿Qué son los relacionados?

El bloque «Sigue leyendo» que cierra un artículo con enlaces a otras entradas del blog, para que el lector tenga a dónde ir cuando termina.

Los relacionados —o «sigue leyendo»— son una lista corta de enlaces a otros artículos que aparece al cerrar la lectura. Cada enlace es el título de otra entrada, con su descripción breve, apuntando a /blog/<slug>. Visto de lejos parece un pie de página más; visto de cerca hace un trabajo concreto: ofrecer el siguiente paso justo cuando el lector decide si se queda o se va.

No se inventó aquí: es un patrón consolidado de blogs y medios —se acaba (o ni siquiera) un artículo y ahí está «esto también te puede interesar»—. En esta plantilla, además, no se escribe a mano: la lista se deriva de la colección de artículos, así que refleja siempre el contenido publicado sin mantener una segunda lista.

Función e importancia

¿Para qué sirve?

Hace tres trabajos a la vez: evita el callejón sin salida al final del artículo, reparte autoridad interna entre las entradas y se alimenta solo de la colección.

Su función es cerrar la brecha entre «terminé de leer» y «¿qué sigue?». Sin relacionados, el artículo desemboca en un muro: el lector se va. Con ellos, el final se vuelve un cruce —dos o tres entradas afines, a un clic—, y la visita encadena páginas en vez de cerrarse. Es, al pie del texto, el mapa de salidas de la lectura.

Y por eso pesa más de lo que aparenta. Para el SEO, es enlazado interno con anchor text real —el título— que reparte autoridad y refuerza el contexto temático entre artículos. Para el lector, es lo que convierte una entrada en un recorrido. Y como sale de la colección, no cuesta mantenimiento: publicar actualiza los candidatos solos. Tres efectos, una sola pieza.

Evita el callejón sin salida

Un artículo que termina sin a dónde ir pierde al lector: se cierra la pestaña y se acaba la visita. Los relacionados ponen el siguiente paso justo al final —dos o tres entradas afines, a un clic—, así la lectura encadena en vez de terminar. Es la diferencia directa entre una visita de una página y un recorrido: más páginas por sesión y menos rebote.

Reparte autoridad interna entre artículos

Cada relacionado es un enlace interno con anchor text real —el título— hacia otro artículo. Para el buscador, eso teje el blog como una malla: distribuye link equity entre las entradas y refuerza el contexto temático de cada una. Un artículo aislado no le pasa autoridad a nadie; uno bien enlazado eleva a sus vecinos y se eleva a sí mismo.

Se alimenta solo (de la colección)

La lista no se escribe a mano artículo por artículo. Sale de la colección de artículos: se filtra el actual y se rebanan unos pocos. Publicar un .mdx lo vuelve candidato en el resto de entradas, sin tocar nada. Cero curaduría manual, cero listas que se desincronizan: el bloque siempre refleja el blog real.

Anatomía

¿Qué lleva el bloque de relacionados?

Cinco piezas: la fuente (la colección), el cálculo (filter + slice 3), el componente que lo pinta (RelatedLinks), el anchor text real (el título) y la posición (el sidebar del artículo).

Cada pieza cumple un papel claro. La fuente define el universo de candidatos; el cálculo decide cuáles y cuántos; el componente los pinta; el anchor text es lo que el lector lee y el buscador rastrea; y la posición es dónde aparecen. Las dos primeras son lógica (invisibles); las tres últimas, lo que se ve en la página.

Abajo, el ejemplo en vivo —réplica anotada del widget «Sigue leyendo»: tres enlaces con su título y su descripción breve—. Cada punto numerado se desglosa en su tarjeta: qué resuelve y de qué dato sale. A diferencia de una lista escrita a mano, aquí los candidatos vienen de la colección, así que publicar los actualiza solos.

1

La fuente (hermanos de la colección)

De dónde salen los candidatos. Hoy son los demás artículos de la colección `articulos` —los hermanos del actual—, leídos una sola vez en getStaticPaths. No hay segunda lista que mantener: el universo de candidatos es el blog entero.

Dato getCollection("articulos")

2

El cálculo (filter + slice)

Cómo se eligen. Se excluye el artículo actual (a.id !== articulo.id) y se toman los tres primeros (slice(0, 3)). De cada uno se arma un objeto { label, href, description }. Es interlinking automático: cero curaduría manual.

Dato filter(a !== actual) · slice(0, 3)

3

El componente (RelatedLinks)

Quién lo pinta. ArticleLayout pasa los relacionados a RelatedLinks como links manuales —cada uno { label, href, desc }—. El componente es genérico: si recibe `links`, los usa tal cual; si no, cae a PRODUCT_CATEGORIES.

Dato links=[{ label, href, desc }]

4

El anchor text (el título real)

Qué dice cada enlace. El texto del enlace es el título del artículo, no «clic aquí» ni «leer más». Ese anchor descriptivo apunta a /blog/<slug> y sirve a la vez al lector (sabe qué abre) y al SEO (da contexto temático).

Dato label = a.data.title → /blog/<slug>

5

La posición (sidebar del artículo)

Dónde aparece. El bloque vive en el sidebar de ArticleLayout, como widget «Sigue leyendo», bajo los temas y sobre el CTA. En móvil el sidebar baja al final, así que cierra la lectura justo donde el lector decide qué sigue.

Dato ArticleLayout · widget «Sigue leyendo»

Variantes

Otros diseños y aplicaciones

La de esta plantilla toma hermanos de la colección, pero la forma de elegir candidatos cambia según el blog: por categoría, por tags, por relación manual, por fecha o por autor.

No hay un único modelo: hay una misma idea —cerrar el artículo con salidas afines— que cada proyecto ajusta cambiando el CRITERIO de selección. Un blog temático prioriza la misma categoría; una base de conocimiento, los tags compartidos; uno editorial, la relación que el autor declara; uno de noticias, lo más reciente; uno multi-autor, la firma.

Abajo, seis variantes —la primera es la real (hermanos de la colección) y las demás son mejoras del cálculo que ajustan cómo se eligen y ordenan los candidatos antes de rebanar—. El componente que las pinta es el mismo; lo que cambia es la lógica que arma la lista. Cada una con su réplica en vivo y el tipo de blog donde rinde mejor.

  • Por colección (hermanos · esta plantilla)

    Blog · Punto de partida

    La actual: toma los demás artículos de la colección, excluye el actual y rebana tres. Es la más simple y la que ya funciona —siempre hay candidatos—, pero no mira el tema: son hermanos, no necesariamente afines. El punto de partida del que salen las demás.

  • Por misma categoría

    Blog temático

    Primero los artículos de la misma categoría que el actual; si faltan, se completa con hermanos. Sube la relevancia sin esfuerzo: quien lee una guía recibe otras guías. Mejora directa del cálculo —filtra por a.data.category antes de rebanar—.

  • Por tags compartidos

    Knowledge base · Afinidad fina

    Ordena los candidatos por cuántas etiquetas comparten con el actual y toma los de mayor solape. Es la afinidad más fina: conecta por concepto transversal, no por categoría única. Requiere puntuar la intersección de tags antes del slice.

  • Por relación manual (frontmatter)

    Editorial · Curado

    El autor declara los relacionados en el frontmatter del .mdx —relatedProducts / relatedServices, o relatedPosts—. Da control editorial total cuando la relevancia automática no basta. Se resuelven esos ids contra la colección y, si quedan huecos, se completan con el cálculo.

  • Más recientes

    Novedades · Frescura

    Los últimos publicados (la colección ya viene ordenada por pubDate), excluyendo el actual. Útil en blogs de noticias donde lo nuevo pesa más que lo afín. Es el mismo slice sobre la colección ya ordenada por fecha.

  • Del mismo autor

    Multi-autor · Columnas

    Otros artículos firmados por el mismo autor del actual. Tiene sentido en blogs con varias plumas o columnas de opinión: quien sigue a un autor encuentra su obra. Filtra por a.data.author antes de rebanar.

Responsive y móvil

Cómo se comporta en el teléfono

En móvil el bloque vive en el sidebar, que se apila al final del artículo: cierra la lectura. Las tarjetas pasan a ancho completo y táctiles, y se mantienen en tres para no alargar.

En escritorio el bloque vive en el sidebar, a la derecha del artículo. En el teléfono no hay ancho para dos columnas, así que el sidebar se apila DEBAJO del contenido —el artículo siempre primero— y «Sigue leyendo» queda al final, justo donde el lector decide qué sigue. Es lo que ya hace el ArticleLayout, sin tocar nada.

A partir de ahí, dos ajustes: las tarjetas pasan a una sola columna, a ancho completo y con área táctil cómoda (la tarjeta entera es el toque, no solo el título); y se limita la lista a tres para no estirar el cierre. El recorte se hace en el cálculo (slice 3), no en CSS. Cada patrón, abajo, con su vista en el teléfono y su receta.

1 · Al final del artículo (default)

El bloque vive en el sidebar; en una columna, el sidebar se apila bajo el contenido. Como en el HTML el artículo va primero, «Sigue leyendo» cae al final con order si hace falta. El lector recibe el texto y, al cerrar, las salidas.

CSS · el bloque baja al final en móvil
/* MÓVIL · el bloque baja AL FINAL del artículo (default).
   Vive en el sidebar; en una sola columna el sidebar se apila
   debajo del contenido, así que «Sigue leyendo» cierra la lectura. */

.post__body { grid-template-columns: 1fr; }   /* una columna en móvil */

/* El artículo va antes que el sidebar en el HTML, de modo que
   los relacionados quedan al final sin tocar nada. */
@media (max-width: 1023px) {
  .post__sidebar { order: 2; }   /* después del contenido */
}

2 · Tarjetas de ancho completo y táctiles

La rejilla pasa a 1fr: una tarjeta por fila, a ancho completo. La tarjeta entera es el área de toque, con al menos 44 px de alto, para acertar con el dedo —no solo el título es enlace—.

CSS · una columna, tarjetas táctiles
/* MÓVIL · cada relacionado ocupa el ancho completo y es táctil.
   La rejilla pasa a una columna y la tarjeta entera es el área
   de toque (≥44 px de alto), no solo el título. */

@media (max-width: 640px) {
  .rel__grid { grid-template-columns: 1fr; }   /* una tarjeta por fila */
  .rel__card { min-height: 44px; padding: var(--sp-4); }
}

3 · Limitar a tres (no alargar)

Tres salidas bastan para ofrecer un siguiente paso sin estirar el cierre en una pantalla angosta. El recorte se hace en el cálculo con slice(3), no en CSS; cualquier extra queda fuera por diseño, no escondido.

CSS · acotar a tres por si acaso
/* Limitar a 3 para no alargar el cierre en móvil.
   El recorte se hace en el cálculo (slice 3), no en CSS; aquí solo
   se acotan por si acaso, ocultando cualquier extra. */

.rel__grid > li:nth-child(n + 4) { display: none; }

Posición

¿Dónde se coloca?

En el sidebar del artículo, como widget «Sigue leyendo», bajo los temas y sobre el CTA. Una vez por artículo —en cada página de /blog/<slug>—, no en el listado.

El bloque entra en el cuerpo del artículo, no en el chrome. Vive en el sidebar de ArticleLayout —junto a la prosa—, como el widget «Sigue leyendo», ubicado bajo los temas (tags) y sobre la caja de CTA. En escritorio acompaña la lectura a la derecha; en móvil, con el sidebar apilado, cierra el artículo al final.

Aparece una sola vez por artículo y no se repite. No va en el listado del blog (ahí el avance lo da la paginación) ni en el chrome global. Su lugar natural es el final de la lectura: el momento exacto en que el lector decide si se queda o se va, y donde un par de salidas afines marcan la diferencia.

Piezas cercanas: el sidebar (la columna donde vive este widget), los artículos (de cuya colección salen los candidatos) y el archivo de categoría (la base del patrón ideal: relacionar por categoría).

Implementación

Cómo está construido

La ruta dinámica [...slug].astro calcula `related` desde la colección (filter + slice 3); ArticleLayout lo pinta en el sidebar con RelatedLinks. La mejora: ordenar por categoría/tags antes de rebanar.

El reparto es deliberado. La ruta dinámica /blog/[...slug].astro lee la colección una vez en getStaticPaths y, por cada artículo, arma su lista de relacionados: excluye el actual y rebana tres, mapeando cada uno a { label, href, description }. Esa lista viaja por props hasta ArticleLayout, que es presentacional —no decide candidatos, solo los pinta—.

El pintado lo hace RelatedLinks, el componente genérico de cross-linking: recibe la lista como `links` manuales y los emite con el título como anchor. El cálculo de hoy es simple (hermanos de la colección); la mejora natural es puntuar cada candidato por misma categoría y tags compartidos antes del slice, para que el orden refleje afinidad y no mera vecindad. Cero JavaScript: todo se genera en build.

Astro · el cálculo de `related` en [...slug].astro
---
// /blog/[...slug].astro · se calcula `related` por artículo en getStaticPaths.
import { getCollection } from 'astro:content'

export async function getStaticPaths() {
  const articulos = await getCollection('articulos', ({ data }) => !data.draft)

  return articulos.map((articulo) => {
    // Relacionados: hermanos de la colección, excluyendo el actual.
    const related = articulos
      .filter((a) => a.id !== articulo.id)
      .slice(0, 3)
      .map((a) => ({
        label: a.data.title,            // anchor text = el título real
        href: `/blog/${a.id}`,          // → /blog/<slug>
        description: a.data.description,
      }))

    return { params: { slug: articulo.id }, props: { articulo, related } }
  })
}
---
Astro · RelatedLinks en el sidebar del artículo
---
// ArticleLayout.astro · el sidebar pinta los relacionados con RelatedLinks.
// Recibe `related` por props y lo pasa como links manuales {label, href, desc}.
import RelatedLinks from '@components/RelatedLinks.astro'
const { related = [] } = Astro.props
---

<aside class="post__sidebar">
  {related.length > 0 && (
    <div class="post__widget">
      <h2>Sigue leyendo</h2>
      <RelatedLinks
        title="Sigue leyendo"
        links={related.map((r) => ({ label: r.label, href: r.href, desc: r.description }))}
      />
    </div>
  )}
</aside>
TypeScript · mejora — ordenar por categoría / tags antes de slice(3)
// MEJORA · ordenar candidatos por afinidad ANTES de slice(3).
// Misma categoría primero; a igualdad, más tags compartidos; luego, más reciente.
// Reemplaza el filter + slice plano por un sort puntuado.
type Art = (typeof articulos)[number]

function relacionar(actual: Art, todos: Art[], n = 3) {
  const tagsActual = new Set(actual.data.tags ?? [])
  const compartidos = (a: Art) =>
    (a.data.tags ?? []).filter((t) => tagsActual.has(t)).length

  return todos
    .filter((a) => a.id !== actual.id)                 // nunca el actual
    .map((a) => ({
      a,
      score:
        (a.data.category === actual.data.category ? 100 : 0) + // misma categoría
        compartidos(a) * 10 +                                  // tags en común
        a.data.pubDate.valueOf() / 1e13,                       // desempate: reciente
    }))
    .sort((x, y) => y.score - x.score)
    .slice(0, n)
    .map(({ a }) => ({ label: a.data.title, href: `/blog/${a.id}`, desc: a.data.description }))
}

En concreto: el cálculo vive en getStaticPaths, así que cada artículo llega a la página con su related ya resuelto —sin trabajo en tiempo de visita—. El componente recorre la lista con Array.prototype.map y solo emite el bloque si related.length > 0, de modo que un blog con un único artículo no deja una caja vacía. El anchor de cada enlace es a.data.title, nunca un texto genérico.

La mejora no cambia la posición ni el componente: cambia el ORDEN de los candidatos. En vez de filter(a !== actual).slice(3), se puntúa cada candidato —misma categoría suma más, luego los tags compartidos, y la fecha desempata— y se rebanan los de mayor afinidad. Como sigue saliendo de la colección, conserva la mejor virtud del patrón: se alimenta solo y no hay listas que mantener a mano.

Buenas prácticas

Qué hacer y qué evitar

La diferencia entre un cierre que retiene y uno que estorba cabe en un puñado de hábitos —empezando por pocos enlaces, afines y con anchor real—.

Ninguno de estos hábitos es capricho: salen de mirar dónde tropieza un blog cuando el cierre crece sin criterio. Un buen bloque de relacionados mantiene tres o cuatro enlaces, usa el título como anchor, excluye el artículo actual y prioriza por categoría o tags. Uno que estorba rellena con cualquier cosa, cura a mano o repite anchors genéricos.

La buena noticia es que casi todo se sostiene solo cuando la lista sale de la colección y el anchor es el título. Abajo, lo que conviene y lo que conviene evitar, enfrentados.

Sí conviene

  • Limita a 3–4 enlaces: los justos para ofrecer salida sin volver el cierre un directorio.
  • Usa como anchor text el título real del artículo, nunca «clic aquí» ni «leer más».
  • Excluye SIEMPRE el artículo actual: no tiene sentido recomendarse a sí mismo.
  • Prioriza por misma categoría o tags compartidos: relevancia antes que mera vecindad.
  • Deriva la lista de la colección (data-driven): una sola fuente, cero mantenimiento.

Mejor evita

  • No rellenes con cualquier cosa: tres relacionados afines valen más que diez al azar.
  • No cures la lista a mano artículo por artículo: se desincroniza al publicar o despublicar.
  • No repitas el artículo actual entre los relacionados (filtra por id).
  • No uses anchors genéricos («aquí», «este post»): pierdes contexto para lector y buscador.
  • No conviertas el cierre en un muro de enlaces: el sidebar ya enlaza categorías y temas.
¿Necesitas ayuda?