DEV Community

Heorhii Lavrynenko
Heorhii Lavrynenko

Posted on Fully Autonomous

Inspeccionar enlaces externos con JavaScript: URLs, atributos rel y límites del DOM

Transparencia: este artículo se ha preparado con ayuda de IA para la redacción y los ejemplos. El código de clasificación se ha comprobado con casos sintéticos; no es una auditoría de sitios de terceros.

Al revisar enlaces conviene separar dos preguntas: «¿a dónde enlaza este documento?» y «¿qué documentos de Internet enlazan a mi dominio?». JavaScript en la página puede ayudar con la primera. La segunda necesita un índice de enlaces o un rastreo con otro alcance.

Este ejemplo es deliberadamente pequeño: recoge enlaces HTTP(S) del documento principal y conserva evidencias, sin asignarles una puntuación SEO.

1. Definir qué significa externo

Aquí usaremos hostname diferente al de la página abierta. Es una decisión explícita, no una definición universal de sitio:

  • blog.example.com es externo respecto a www.example.com.
  • Un puerto diferente con el mismo hostname no es externo para esta regla.
  • No eliminamos www ni calculamos el dominio registrable. Agrupar subdominios necesitaría otra política y, para hacerlo correctamente, una lista de sufijos públicos.

No basta con buscar si un href contiene el nombre del dominio: example.com.evil.test y evil.test/?next=example.com darían falsos resultados.

2. Resolver la URL y conservar los atributos

La función recibe la base de resolución y el hostname de comparación por separado. Un <base href> puede cambiar el destino de un enlace relativo, pero no debería cambiar qué página estamos inspeccionando.

function externalLinkRecord(rawHref, baseURI, pageHostname, rel = '', text = '') {
  if (!rawHref || !rawHref.trim()) return null;

  let url;
  try {
    url = new URL(rawHref, baseURI);
  } catch {
    return null;
  }

  if (!['http:', 'https:'].includes(url.protocol)) return null;
  if (url.hostname === pageHostname) return null;

  const tokens = new Set(
    rel.toLowerCase().split(/[\t\n\f\r ]+/).filter(Boolean)
  );

  return {
    url: url.href,
    hostname: url.hostname,
    anchor: text.replace(/\s+/g, ' ').trim(),
    rel,
    nofollow: tokens.has('nofollow'),
    sponsored: tokens.has('sponsored'),
    ugc: tokens.has('ugc')
  };
}
Enter fullscreen mode Exit fullscreen mode

new URL() resuelve referencias relativas y rechaza entradas no válidas. Filtrar el protocolo evita mezclar enlaces web con mailto:, tel: o javascript:. Los atributos se guardan como señales independientes: un enlace puede tener sponsored y nofollow simultáneamente.

3. Obtener una instantánea del documento

Este segundo bloque utiliza la función anterior:

const records = [...document.querySelectorAll('a[href]')]
  .map(a => externalLinkRecord(
    a.getAttribute('href'),
    document.baseURI,
    location.hostname,
    a.getAttribute('rel') || '',
    a.textContent || ''
  ))
  .filter(Boolean);

console.table(records);

const groups = new Map();
for (const record of records) {
  const group = groups.get(record.hostname) || [];
  group.push(record);
  groups.set(record.hostname, group);
}

console.table([...groups].map(([hostname, links]) => ({
  hostname,
  occurrences: links.length,
  uniqueURLs: new Set(links.map(link => link.url)).size
})));
Enter fullscreen mode Exit fullscreen mode

Aquí anchor significa texto del nodo, no nombre accesible. Un enlace que solo contiene una imagen puede tener anchor vacío y seguir siendo perfectamente utilizable. Tampoco filtramos por visibilidad: textContent puede incluir texto oculto.

El total cuenta apariciones, mientras que uniqueURLs cuenta URLs serializadas distintas. No eliminamos fragmentos ni parámetros: decidir que dos destinos son equivalentes requiere una regla adicional. Repetir el mismo destino en el menú y el pie de página no demuestra un problema.

4. Casos pequeños que conviene probar

Con base https://example.com/docs/ y hostname example.com:

Entrada Resultado esperado
/contacto/ Se excluye: mismo hostname
https://example.com:8443/ Se excluye con esta regla
//other.test/help Se incluye como HTTPS
https://blog.example.com/ Se incluye: subdominio distinto
https://example.com.evil.test/ Se incluye: otro hostname
mailto:hello@example.com Se excluye por protocolo
http://[ Se excluye: URL no válida

Añade una prueba con rel="UGC nofollow sponsored": las tres señales deben resultar verdaderas. Y prueba una base externa: un enlace relativo resuelto contra ella puede ser externo aunque su texto original no contenga un dominio.

5. Lo que este resultado no demuestra

La consulta devuelve una instantánea: los enlaces añadidos después por JavaScript requieren otra lectura. No entra en iframes ni atraviesa Shadow DOM. No extrae enlaces de botones, manejadores de eventos ni todo el historial de navegación de una aplicación.

Tampoco realiza solicitudes HTTP, sigue redirecciones, comprueba indexación o descubre backlinks. Un enlace sin esos tres atributos rel no garantiza transferencia de PageRank. Una señal aislada no sustituye la revisión del destino y del contexto.

Si amplías el ejemplo con CSV, trata las celdas como datos no confiables: las hojas de cálculo pueden interpretar texto que empiece por ciertos caracteres como fórmulas. Entrecomillar valores no elimina por sí solo ese riesgo. Este ejemplo se queda en console.table para mantener el alcance visible.

La parte útil de una herramienta de inspección empieza en su contrato: qué documento lee, cómo decide qué es externo y qué conclusiones deja deliberadamente fuera.

Referencias: constructor URL en MDN y hostname de los enlaces en MDN.

Top comments (0)