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èmrel="noopener", trang được mở có thể truy cậpwindow.openervà 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:
- Dùng filter
the_contentđể can thiệp vào HTML bài viết trước khi render ra frontend. - Dùng
DOMDocumentđể parse HTML, duyệt tất cả thẻ<a>. - Phân biệt link nội bộ và link ngoài dựa trên domain của
home_url(). - Với link ngoài, thêm
nofollowvànoopenervào thuộc tínhrelnếu chưa có. - Giữ nguyên các giá trị
relcũ (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