Replace anchor tags and force create new tag with custom attributes I'm just use the add_filter('the_content', function($content){}); for filtering anchor tags in a post.

Basically, I want filter anchor tags to appropriate format. I want to add custom attributes like rel="nofollow noopener" and target="_blank" if the anchor tag linked to external of my WordPress site.

Instead checking and replace if rel or target is already exists in the anchor tag, I just need to retrieve the value of href and the value of the anchor tag itself (&lt;a&gt;value&lt;/a&gt;). So I can force create an anchor tags with my preferred custom attributes.

Here's my current working function:

add_filter('the_content', function($content) {
    $internal = get_home_url();

    return preg_replace_callback('#&lt;a.*?href="([^"]*)".*?&gt;([^&gt;]*)&lt;/a&gt;#i', function($match) use ($internal) {
        if(stripos($match[1], $internal) || strpos($match[1], "#") == 0) {
            return "&lt;a href=\"$match[1]\"&gt;$match[2]&lt;/a&gt;";
        } else {
            return "&lt;a href=\"$match[1]\" rel=\"nofollow noopener\" target=\"_blank\"&gt;$match[2]&lt;/a&gt;";
        }
    }, $content);
});


I'm worried about performance because this function run each user read a post. Any suggestions are welcome.
I must recommend that you favor accuracy over performance.  After all, what good is a fast loading site if it doesn't provide the correct/intended content.

When parsing valid html, please always leverage a good dom parser.  Regex is "dom-unaware" and therefore is more vulnerable to breakage.

Here is a demonstration of how to use DOMDocument and XPath to articulately replace hyperlinks in your document in a stable manner:

Code: (Demo)

$html = &lt;&lt;&lt;HTML
&lt;div&gt;
&lt;a href="#"&gt;hello&lt;/a&gt; &lt;abbr href="sneaky.com"&gt;FYI&lt;/abbr&gt; &lt;a title="goodbye"&gt;later&lt;/a&gt;
&lt;a href=https://example.com&gt;no quoted attributes&lt;/a&gt;
&lt;A href="https://external.com"
title="some title"
data-key="{\'key\':\'adf0a8dfq&lt;&gt;*1$4%\'"&gt;a link with data attribute&lt;/A&gt;
and
this is &lt;a title="hello"&gt;not a hyperlink&lt;/a&gt; but simply an anchor tag
&lt;a href="#jumpTo"&gt;Jumper&lt;/a&gt;
&lt;/div&gt;
HTML;

$internal = 'https://example.com';

$dom = new DOMDocument; 
$dom-&gt;loadHTML($html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
$xpath = new DOMXPath($dom);
foreach ($xpath-&gt;query("//a[@href]") as $node) {
    $replacementNode = $dom-&gt;createElement("a", $node-&gt;nodeValue);
    $href = $node-&gt;getAttribute('href');
    $replacementNode-&gt;setAttribute('href', $href); 
    if($href[0] != "#" &amp;&amp; stripos($href, $internal) === false) {
        $replacementNode-&gt;setAttribute('rel', 'nofollow noopener');
        $replacementNode-&gt;setAttribute('target', '_blank');
    }
    $node-&gt;parentNode-&gt;replaceChild($replacementNode, $node);
}
echo $dom-&gt;saveHTML();


Output:

&lt;div&gt;
&lt;a href="#"&gt;hello&lt;/a&gt; &lt;abbr href="sneaky.com"&gt;FYI&lt;/abbr&gt; &lt;a title="goodbye"&gt;later&lt;/a&gt;
&lt;a href="https://example.com"&gt;no quoted attributes&lt;/a&gt;
&lt;a href="https://external.com" rel="nofollow noopener" target="_blank"&gt;a link with data attribute&lt;/a&gt;
and
this is &lt;a title="hello"&gt;not a hyperlink&lt;/a&gt; but simply an anchor tag
&lt;a href="#jumpTo"&gt;Jumper&lt;/a&gt;
&lt;/div&gt;


Will this be slow?  Well, I presume it will be slower than regex but then that is the cost of implementing a superior processing tool.  If you need to gain performance, investigate other avenues which will not spoil your content.  This is not a task where a shortcut is a good idea.