Tự động phát hiện link ngoài và thêm rel=”nofollow noopener” trong WordPress

Trong bài viết WordPress, bạn thường chèn rất nhiều link ra ngoài: tài liệu tham khảo, công cụ, blog khác. Nếu không kiểm soát thuộc tính rel cho những link này, bạn có thể vừa thất thoát “độ uy tín” SEO, vừa mở ra một lỗ hổng nhỏ về bảo mật (tabnabbing) khi dùng target="_blank". Bài này hướng dẫn bạn cách tự động detect link ngoài trong nội dung bài viết và thêm rel="nofollow noopener" bằng một snippet PHP, với prefix hàm là init_* cho đúng chuẩn.

Tự động phát hiện link ngoài và thêm rel=”nofollow noopener” trong WordPress

Vì sao cần xử lý link ngoài trong WordPress

Khi bạn chèn link ra domain khác, có ba lý do chính để kiểm soát thuộc tính rel:

  • SEO: rel="nofollow" báo cho máy tìm kiếm rằng bạn không “truyền” tín hiệu xếp hạng cho link đó. Dùng hợp lý sẽ giúp kiểm soát dòng chảy PageRank.
  • Bảo mật: khi dùng target="_blank" mà không đi kèm rel="noopener", trang được mở có thể truy cập window.opener và thao tác ngược lại trên site của bạn (tabnabbing).
  • Quy chuẩn: nhiều team content muốn: link nội bộ để dofollow, link ngoài từ nguồn không kiểm soát thì nofollow + noopener.

Thay vì yêu cầu từng người viết nhớ đặt tay, bạn có thể xử lý tự động ở tầng code.

Chiến lược: lọc nội dung bằng filter the_content

Ý tưởng chung:

  1. Dùng filter the_content để can thiệp vào HTML bài viết trước khi render ra frontend.
  2. Dùng DOMDocument để parse HTML, duyệt tất cả thẻ <a>.
  3. Phân biệt link nội bộ và link ngoài dựa trên domain của home_url().
  4. Với link ngoài, thêm nofollownoopener vào thuộc tính rel nếu chưa có.
  5. Giữ nguyên các giá trị rel cũ (ví dụ: ugc, sponsored) bằng cách merge thay vì overwrite.

Cách làm này giúp bạn xử lý tập trung, không phụ thuộc vào editor đang dùng Classic hay Gutenberg.

Snippet: tự động thêm rel=”nofollow noopener” cho link ngoài

Đoạn code bên dưới có prefix đầy đủ với init_*, bạn có thể đặt trong functions.php của theme child, hoặc tốt hơn là trong một plugin riêng.

<?php
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

/**
 * Kiểm tra một URL có phải link nội bộ site hiện tại hay không.
 */
function init_is_internal_url( $url ) {
    $url = trim( (string) $url );
    if ( $url === '' ) {
        return true;
    }

    // Link tương đối (không có schema, domain) coi như nội bộ
    if ( strpos( $url, '//' ) === false ) {
        return true;
    }

    $site_url  = home_url();
    $site_host = parse_url( $site_url, PHP_URL_HOST );
    $url_host  = parse_url( $url, PHP_URL_HOST );

    if ( ! $site_host || ! $url_host ) {
        return true;
    }

    // So sánh host, có thể mở rộng thêm subdomain nếu cần
    return strtolower( $site_host ) === strtolower( $url_host );
}

/**
 * Tự động thêm rel="nofollow noopener" cho link ngoài trong nội dung bài viết.
 */
function init_filter_external_links_add_rel( $content ) {
    if ( is_admin() || wp_doing_ajax() ) {
        return $content;
    }

    if ( trim( $content ) === '' ) {
        return $content;
    }

    // Đảm bảo có extension DOMDocument
    if ( ! class_exists( 'DOMDocument' ) ) {
        return $content;
    }

    // Gói nội dung để DOMDocument parse
    $html = '<div id="init-external-link-wrapper">' . $content . '</div>';

    $dom                     = new DOMDocument();
    $dom->preserveWhiteSpace = false;
    $dom->formatOutput       = false;

    // Chuyển encoding để hạn chế lỗi ký tự
    $internal_errors = libxml_use_internal_errors( true );
    $loaded          = $dom->loadHTML(
        mb_convert_encoding( $html, 'HTML-ENTITIES', 'UTF-8' )
    );
    libxml_clear_errors();
    libxml_use_internal_errors( $internal_errors );

    if ( ! $loaded ) {
        return $content;
    }

    $links = $dom->getElementsByTagName( 'a' );

    foreach ( $links as $link ) {
        $href = $link->getAttribute( 'href' );

        // Bỏ qua link không có href
        if ( ! $href ) {
            continue;
        }

        // Bỏ qua link nội bộ
        if ( init_is_internal_url( $href ) ) {
            continue;
        }

        // Đã là link ngoài: xử lý thuộc tính rel
        $rel_attr = $link->getAttribute( 'rel' );
        $tokens   = preg_split( '/\s+/', trim( $rel_attr ) );
        $tokens   = array_filter( (array) $tokens );

        // Thêm nofollow và noopener nếu chưa có
        if ( ! in_array( 'nofollow', $tokens, true ) ) {
            $tokens[] = 'nofollow';
        }
        if ( ! in_array( 'noopener', $tokens, true ) ) {
            $tokens[] = 'noopener';
        }

        $tokens = array_unique( $tokens );
        $link->setAttribute( 'rel', implode( ' ', $tokens ) );
    }

    // Lấy lại phần HTML bên trong wrapper
    $wrapper = $dom->getElementById( 'init-external-link-wrapper' );
    if ( ! $wrapper ) {
        return $content;
    }

    $new_html = '';
    foreach ( $wrapper->childNodes as $child ) {
        $new_html .= $dom->saveHTML( $child );
    }

    return $new_html;
}

add_filter( 'the_content', 'init_filter_external_links_add_rel', 20 );

Đoạn code trên chỉ tập trung vào việc thêm rel="nofollow noopener" cho link ngoài, không can thiệp link nội bộ. Nếu sau này bạn muốn bổ sung logic, chỉ cần sửa hàm init_is_internal_url().

Mở rộng: kiểm soát thêm target và whitelist domain

Trong nhiều trường hợp, bạn muốn link ngoài luôn mở ở tab mới, đồng thời vẫn giữ whitelist cho một số domain đối tác hoặc dịch vụ tin cậy. Bạn có thể mở rộng snippet trên bằng hai bước:

  • Buộc target="_blank" với link ngoài khi chưa có target.
  • Tạo danh sách whitelist, nếu domain nằm trong whitelist thì bỏ qua việc thêm nofollow, chỉ giữ noopener.

Dưới đây là ví dụ mở rộng hàm kiểm tra domain:

<?php
function init_is_whitelisted_external_domain( $url ) {
    $whitelist = [
        'youtube.com',
        'github.com',
        // Thêm các domain bạn tin cậy
    ];

    $host = parse_url( $url, PHP_URL_HOST );
    if ( ! $host ) {
        return false;
    }

    $host = strtolower( $host );

    foreach ( $whitelist as $allowed ) {
        $allowed = strtolower( $allowed );
        if ( $host === $allowed || substr( $host, - strlen( $allowed ) ) === $allowed ) {
            return true;
        }
    }

    return false;
}

Sau đó, trong vòng lặp xử lý <a>, bạn có thể thêm điều kiện:

// Nếu là link ngoài nhưng nằm trong whitelist, chỉ thêm noopener
if ( init_is_whitelisted_external_domain( $href ) ) {
    $rel_attr = $link->getAttribute( 'rel' );
    $tokens   = preg_split( '/\s+/', trim( $rel_attr ) );
    $tokens   = array_filter( (array) $tokens );

    if ( ! in_array( 'noopener', $tokens, true ) ) {
        $tokens[] = 'noopener';
    }

    $tokens = array_unique( $tokens );
    $link->setAttribute( 'rel', implode( ' ', $tokens ) );

    // Có thể buộc target="_blank" nếu muốn
    if ( ! $link->getAttribute( 'target' ) ) {
        $link->setAttribute( 'target', '_blank' );
    }

    continue;
}

Bằng cách này, bạn vẫn kiểm soát tốt bảo mật và trải nghiệm, trong khi không bị “quá tay” với những domain đối tác cần giữ dofollow.

Kết luận

Tự động detect link ngoài và thêm rel="nofollow noopener" là một bước nhỏ nhưng có tác động lâu dài tới SEO và bảo mật site WordPress. Với một snippet duy nhất, bạn giải quyết được ba vấn đề: chuẩn hóa link ngoài, giảm rủi ro tabnabbing và giúp team content tập trung vào nội dung thay vì cấu hình từng link. Việc dùng prefix init_* cũng giúp code của bạn dễ nhận diện, dễ bảo trì khi dự án ngày càng lớn.

Bình luận


  • Không có bình luận.

Init Toolbox

Nhấn Ctrl + \ trên máy tính, hoặc vuốt sang trái ở bất kỳ đâu trên mobile.

Đăng nhập





Đang tải...