From 7486849f336f349c40afac33391df71fb279485b Mon Sep 17 00:00:00 2001 From: Julio Lucero Date: Tue, 30 Jun 2026 11:41:14 -0300 Subject: [PATCH 1/2] Detect external links by host instead of url prefix --- README.md | 6 ++++- lib/jekyll-external-link-accessibility.rb | 26 ++++++++++++++++++- .../hooks.rb | 4 ++- .../version.rb | 2 +- 4 files changed, 34 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 52d479b..0598c9d 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,11 @@ This plugin adds `rel`, `title`, `new tab icon` and `target` to all external lin ``` ## Usage -The plugin automatically edits all links on all posts. You can however skip the check on some links, by adding the `data-no-external` attribute and setting it to `true`, e.g `...` to the link. +The plugin automatically edits external links on all posts. A link is considered external only when it points to a host other than your site's `url` (set in `_config.yml`). + +- Relative links (`/blog/...`) and absolute links to your own domain are left untouched, so they keep their link equity and open in the same tab. +- The `www.` prefix is ignored when comparing hosts. +- Skip a specific link by adding `data-no-external="true"`, e.g. `...`. ### Configuration You can override the default configuration by adding the following section to your Jekyll site's `_config.yml`: diff --git a/lib/jekyll-external-link-accessibility.rb b/lib/jekyll-external-link-accessibility.rb index 837f584..c854bfb 100644 --- a/lib/jekyll-external-link-accessibility.rb +++ b/lib/jekyll-external-link-accessibility.rb @@ -1,15 +1,19 @@ require 'jekyll-external-link-accessibility/hooks' require 'nokogiri' +require 'uri' module Jekyll class ExternalLinkAccessibility + EXTERNAL_SCHEMES = %w[http:// https:// //].freeze + def self.modify_links(page) config = page.site.config + site_host = host_for(config['url']) doc = Nokogiri::HTML5(page.output) doc.css('.post-content a, .post-excerpt a').each do |a| next if a['href'].nil? || a['href'].empty? || a['href'].start_with?('#') || a['data-no-external'] == 'true' - if a['href'].start_with?('http') || a['href'].start_with?('/') + if external_link?(a['href'], site_host) a['rel'] = external_link_rel(config: config) unless a['rel'] a['target'] = external_link_target(config: config) unless a['target'] a['title'] = external_link_title(config: config) unless a['title'] @@ -30,6 +34,26 @@ def self.modify_links(page) page.output = doc.to_html end + # A link is external only when it points to a different host than the site. + # Relative links ("/blog/...") and absolute links to our own domain are internal, + # so they keep their link equity and stay in the same tab. + def self.external_link?(href, site_host) + return false unless href.start_with?(*EXTERNAL_SCHEMES) + + link_host = host_for(href) + return true if link_host.nil? + + link_host != site_host + end + + # Returns the host without a leading "www." so www and non-www match. + def self.host_for(url) + host = URI.parse(url.to_s).host + host&.downcase&.sub(/\Awww\./, '') + rescue URI::InvalidURIError + nil + end + def self.external_link_rel(config:) config.dig('external_links', 'rel') || 'external nofollow noopener noreferrer' end diff --git a/lib/jekyll-external-link-accessibility/hooks.rb b/lib/jekyll-external-link-accessibility/hooks.rb index 88111bf..f9ae3c2 100644 --- a/lib/jekyll-external-link-accessibility/hooks.rb +++ b/lib/jekyll-external-link-accessibility/hooks.rb @@ -6,6 +6,8 @@ end Jekyll::Hooks.register :pages, :post_render do |page| - next if %w[.scss .json .xml].include?(page.extname) + # Only rewrite HTML pages. Running the HTML parser over .xml (rss/sitemap), + # .json, .txt, etc. would wrap them in and corrupt them. + next unless ['.html', '.htm'].include?(page.extname) Jekyll::ExternalLinkAccessibility.modify_links(page) end diff --git a/lib/jekyll-external-link-accessibility/version.rb b/lib/jekyll-external-link-accessibility/version.rb index d7543a4..9683a5d 100644 --- a/lib/jekyll-external-link-accessibility/version.rb +++ b/lib/jekyll-external-link-accessibility/version.rb @@ -2,6 +2,6 @@ module Jekyll class ExternalLinkAccessibility - VERSION = '0.1.0' + VERSION = '0.2.0' end end From e323c43c583fb436d7a249e19589d34fde344f5e Mon Sep 17 00:00:00 2001 From: Julio Lucero Date: Wed, 8 Jul 2026 16:59:09 -0300 Subject: [PATCH 2/2] Open internal links in a new tab without nofollow --- README.md | 2 +- lib/jekyll-external-link-accessibility.rb | 31 +++++++++++++---------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 0598c9d..7048ecb 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ This plugin adds `rel`, `title`, `new tab icon` and `target` to all external lin ## Usage The plugin automatically edits external links on all posts. A link is considered external only when it points to a host other than your site's `url` (set in `_config.yml`). -- Relative links (`/blog/...`) and absolute links to your own domain are left untouched, so they keep their link equity and open in the same tab. +- Relative links (`/blog/...`) and absolute links to your own domain are left untouched, so they keep their link equity. They still open in a new tab, but without the `nofollow` rel or the external-link icon. - The `www.` prefix is ignored when comparing hosts. - Skip a specific link by adding `data-no-external="true"`, e.g. `...`. diff --git a/lib/jekyll-external-link-accessibility.rb b/lib/jekyll-external-link-accessibility.rb index c854bfb..9d6e060 100644 --- a/lib/jekyll-external-link-accessibility.rb +++ b/lib/jekyll-external-link-accessibility.rb @@ -13,22 +13,27 @@ def self.modify_links(page) doc.css('.post-content a, .post-excerpt a').each do |a| next if a['href'].nil? || a['href'].empty? || a['href'].start_with?('#') || a['data-no-external'] == 'true' + # Every link opens in a new tab so readers don't lose their place, and + # screen readers are told a new window opens. + a['target'] = external_link_target(config: config) unless a['target'] + a['title'] = external_link_title(config: config) unless a['title'] + + a.add_child( + " + opens a new window + " + ) + + # For external links, add rel="external nofollow noopener noreferrer" and an icon. if external_link?(a['href'], site_host) a['rel'] = external_link_rel(config: config) unless a['rel'] - a['target'] = external_link_target(config: config) unless a['target'] - a['title'] = external_link_title(config: config) unless a['title'] a.add_child(" ") - a.add_child( - " - opens a new window - " - ) end end page.output = doc.to_html