Open Wagtail Rich Text Block Links in a New Tab

Introduction

You'd think being able to set external link targets in rich text blocks to open in a new tab would be something offered out of the box. I put this in as a feature request with Wagtail which was promptly shut down with the explanation that "it's against UX best practice" and that it should be up to the visitor to decide if they want to open in a new tab.

Personally, I find it sloppy when a link to an external site isn't opened in a new tab by default. I've lost my place in the site I was on, and now have to navigate back or reload from history. If the opened page is something I want to keep open and save for later while I continue on the original site, then the new tab is the only route for this. I'm more likely to just not return to the referring page.

Much more importantly, leaving your site to visit another is extremely bad practice from a business/marketing/retention viewpoint where the very aim is to retain the visitor, not to send them elsewhere.

fa-solid fa-circle-info fa-xl Clarification
This applies only to links created using the External Link option in Wagtail's built-in rich text block editor (Draftail). Links that you create in custom blocks or any other way are entirely up to the developer. Since linking from text is such a common feature, this requires extra consideration.

Workaround

There are two routes that I've come across to circumvent this: adding a site-wide policy via wagtail hooks or using a simple block of JavaScript on the document ready event to modify links that match the criteria.

A note about the rel attribute

When opening a link to an external site, I add two attributes to the <a> element: rel="nofollow noopener"

These are optional and included for the following reasons:

noopener - this closes down an old security flaw and instructs the browser to navigate to the target resource without granting the new browsing context access to the document that opened it by not setting the Window.opener property on the opened window (it returns null). This is included for older browsers where target="_blank" used an opener which was vulnerable to exploit. noopener is now the default behaviour in all major browsers. See here for a discussion on this.

nofollow - instructs web crawlers to not follow the link. A couple of reasons for doing this: it avoids circular references (a link to a page that links back to the current page) which will score negative points on your SEO; it avoids giving SEO to your competitors (not sure why you'd want to link to your competitors, but it's here for completeness). Leave this off if this is not what you want.

Read more about the rel attribute on Mozilla's developer documentation.

Opening Links in New Tabs via JavaScript

This is a very simple override that amends the attributes of your href a tags if they point to an address that includes the http or https protocols. There are a couple of benefits to using this over a site-wide policy that affects every 'external link':

  1. In a site where there are Django pages alongside Wagtail pages (such as this one), linking to your internal Django pages has to be done using the External Link option since they don't feature in the Page Chooser. These should be opened in the same tab, only opening truly external links in new tabs. The JavaScript method allows this, provided you use relative paths for internal URL's since relative links don't include the protocol. You may also be using the External Link option to point to aliased domains or routable pages, all of which you'd want in the same tab.
  2. You can apply this method to just the page types you want, rather than the entire site.
  3. It doesn't involve messing with Wagtail internals which can potentially break with future updates.
Copy
document.querySelectorAll('a[href^="http"]').forEach(link => {
    link.setAttribute("target", "_blank");
    link.setAttribute("rel", "nofollow noopener")
});
Opening document links in new tabs

The above code is just looking at external links. Document links created in Draftail will also open the document in the same browser tab by default. To open these in a new tab as well, amend the line above to:

Copy
document.querySelectorAll('a[href^="http"], a[href^="/documents/"]').forEach(link => {
  link.setAttribute('target', '_blank');
  link.setAttribute('rel', 'nofollow noopener');
});

The noopener rel attribute is redundant for document links but doesn't have any negative affects. nofollow will stop your document being indexed by search engines, which you may or may not want.

For me, the problem in Wagtail exists that when a document is updated, the URL changes. If that document is indexed, Google Search Console will start complaining about a 404 error. You can create permanent redirects, but it gets a bit messy having daisy chains of redirects, I'd rather just not index the document. Hopefully, a future release of Wagtail will use a slug for document URL's that remains static.

If you want to have your document links open in a new tab but also indexed, just split the above into 2 lines and remove the rel attributes for documents.

That's all there is to it. I've dropped this into my site-wide JavaScript as I don't have any special requirements. It doesn't mess with the OAuth links and leaves my "internal external" Django links alone as well.

Opening Links in New Tabs via Wagtail Hooks

Doing this will apply a site-wide policy on all 'External Links', even those that point to internal links.

Starting with Wagtail 2.5 the following is possible in your wagtail_hooks.py:

Copy
from django.utils.html import escape
import wagtail

if wagtail.VERSION < (5, 0):
    from wagtail.core import hooks
    from wagtail.core.rich_text import LinkHandler
else:
    from wagtail import hooks
    from wagtail.rich_text import LinkHandler

class NewWindowExternalLinkHandler(LinkHandler):
    # This specifies to do this override for external links only.
    # Other identifiers are available for other types of links.
    identifier = 'external'

    @classmethod
    def expand_db_attributes(cls, attrs):
        href = attrs["href"]
        # Let's add the target attr, and also rel="noopener" + noreferrer fallback.
        # See https://github.com/whatwg/html/issues/4078.
        return '' % escape(href)

@hooks.register('register_rich_text_features')
def register_external_link(features):
    features.register_link_type(NewWindowExternalLinkHandler)

* Note the import path changed with Wagtail 5.

Now in your text block declaration, remove the 'link' option and add 'extended_link'.

For example:

Copy
body = blocks.RichTextBlock(
    features=['h1', 'h2', 'h3', 'h4', 'bold', 'italic', 'ol', 'ul', 'hr', 'extended_link', 'image']
)

Summary

Well, my vote goes with the JavaScript option for the reasons listed above. It's simple, non-intrusive and only applies to those 'external links' while leaving relative links alone.

Should it break, your links simply open in the same tab. Nothing disastrous. It's the method applied to this website.


  Please feel free to leave any questions or comments below, or send me a message here