KIếN THứC WEBSITE › WORDPRESS

Enqueue script style WordPress đúng cách — 4 hook + conditional load

Enqueue script style WordPress đúng cách — 4 hook + conditional load 2026

Nhồi <script> hay <link> trực tiếp vào header.php là cách nhanh nhất phá browser cache, conflict version với plugin, và bị Lighthouse audit mark non-optimal. Bài này hệ thống lại đúng pattern wp_enqueue_*: phân biệt register vs enqueue, 4 hook theo context request, conditional load theo template, defer/async để giảm LCP, dequeue plugin asset không cần, và versioning cache busting bền vững.

Phân biệt wp_enqueue_script và wp_register_script

enqueue script style wordpress — Phân biệt wp_enqueue_script và wp_register_script
Phân biệt wp_enqueue_script và wp_register_script

WordPress tách hai bước rõ ràng: register (khai báo asset tồn tại) và enqueue (insert vào HTML output). Hiểu rõ phân biệt này tránh duplicate enqueue và mở khoá pattern reuse handle ở nhiều chỗ.

wp_register_script($handle, $src, $deps, $ver, $in_footer) chỉ khai báo, không insert. wp_enqueue_script($handle) mới thực sự đẩy asset vào HTML output trong hook tương ứng.

Pattern register sớm, enqueue trễ

Cộng đồng WordPress khuyến nghị pattern này cho theme có nhiều asset. Register một lần sớm trong hook wp_enqueue_scripts priority thấp, sau đó enqueue có điều kiện trong template hoặc hook trễ hơn.

  • Tránh duplicate enqueue: 1 handle dù gọi enqueue 5 lần chỉ insert 1 lần — pattern register tách biệt giúp gọi enqueue tự do mà không sợ output trùng.
  • Reuse cross-template: template page, single, archive đều enqueue cùng handle — không cần lặp lại config src và deps.
  • Asset điều kiện trong shortcode: shortcode chỉ enqueue khi user thực sự dùng — tiết kiệm 30-80KB cho page không có shortcode.

Khi register-only không enqueue

Asset có thể chỉ register làm dependency cho asset khác mà không cần load chính nó trực tiếp. Pattern này hữu ích cho helper library dùng nội bộ.

Ví dụ: register w22-utils chứa helper function, sau đó w22-form depend w22-utils. Khi enqueue w22-form, WordPress tự động enqueue w22-utils trước.

Bốn hook đúng để enqueue theo context request

Bốn hook đúng để enqueue theo context request
Sơ đồ minh hoạ — Bốn hook đúng để enqueue theo context request
Bốn hook đúng để enqueue theo context request
Sơ đồ minh hoạ — Bốn hook đúng để enqueue theo context request

Sai hook đồng nghĩa asset không xuất hiện đúng chỗ — frontend asset hiện trong admin gây lag, hoặc admin asset miss khi vào màn hình post edit. Bốn hook dưới phủ 99% nhu cầu thực tế.

Quy tắc chung: chọn hook hẹp nhất phù hợp với context cần load. Đừng dùng init hoặc wp_head trực tiếp — đó là anti-pattern bypass cơ chế WordPress.

wp_enqueue_scripts — hook cho frontend

99% trường hợp frontend dùng hook này. Chạy trên mọi page frontend, không chạy admin, không chạy login.

Đây là hook phổ thông nhất em sẽ dùng hằng ngày.

add_action('wp_enqueue_scripts', function () {
    wp_enqueue_style(
        'w22-tokens',
        get_template_directory_uri() . '/assets/css/tokens.css',
        [],
        W22_VERSION
    );
    wp_enqueue_script(
        'w22-main',
        get_template_directory_uri() . '/assets/js/main.js',
        [],
        W22_VERSION,
        true
    );
});

admin_enqueue_scripts — hook cho admin

Hook này nhận parameter $hook_suffix giúp check màn hình cụ thể. Bỏ check đồng nghĩa load asset trên mọi admin page — kéo admin lag rõ rệt.

add_action('admin_enqueue_scripts', function ($hook) {
    if ($hook !== 'post.php' && $hook !== 'post-new.php') return;
    if (get_post_type() !== 'service') return;
    wp_enqueue_script('w22-service-meta', /* ... */);
});

enqueue_block_editor_assets — hook Block Editor

Asset chỉ load trong Block Editor canvas (Gutenberg). Dùng cho custom block React, panel sidebar tuỳ biến, custom format text.

Phân biệt với enqueue_block_assets — hook này chạy cả frontend và editor, dùng cho style block hiển thị cả 2 chỗ. Sai hook hai cái này làm style block không hiện trên frontend hoặc editor không preview đúng.

login_enqueue_scripts — hook wp-login.php

Custom branding login screen như logo, màu nền, ảnh background. Asset không hiện ở admin hay frontend, chỉ chạy ở wp-login.php — phạm vi rất hẹp nhưng cần thiết cho theme có brand riêng.

Conditional enqueue — chỉ load asset cần cho template hiện tại

Theme trung bình có 10-14 file CSS template-specific. Load toàn bộ trên mọi page đồng nghĩa waste 150-200KB CSS không dùng cho mỗi visit — phá Lighthouse Performance score.

Conditional check ngay trong hook wp_enqueue_scripts giảm payload 40-60% cho theme có nhiều template. Pattern rất đơn giản, chỉ cần if check trước khi gọi wp_enqueue_style.

Pattern conditional check theo template

Đoạn code dưới minh hoạ split asset thành 2 nhóm: nhóm always (tokens, layout) load mọi page, nhóm conditional load theo template cụ thể.

add_action('wp_enqueue_scripts', function () {
    // Always-load assets
    wp_enqueue_style('w22-tokens', '/* ... */tokens.css');
    wp_enqueue_style('w22-layout', '/* ... */layout.css');

    // Per-template assets
    if (is_front_page()) {
        wp_enqueue_style('w22-home', '/* ... */home.css');
    }
    if (is_singular('service')) {
        wp_enqueue_style('w22-service', '/* ... */single-service.css');
    }
    if (is_singular('case_study')) {
        wp_enqueue_style('w22-case', '/* ... */case-study.css');
    }
    if (is_page('lien-he')) {
        wp_enqueue_script('w22-form', '/* ... */form.js', [], W22_VERSION, true);
    }
});

has_block() cho block-specific asset

has_block('namespace/block-slug') trả về true nếu post hiện tại có block đó. Pattern này giảm CSS load 40-60% trên page không có block cụ thể.

add_action('wp_enqueue_scripts', function () {
    if (has_block('carbon-fields/web22-hero')) {
        wp_enqueue_style('w22-hero', /* ... */);
    }
    if (has_block('carbon-fields/web22-pricing-table')) {
        wp_enqueue_style('w22-pricing', /* ... */);
    }
});

Pitfall conditional check phổ biến

Conditional check sai context dẫn tới asset miss ở place cần. Hai pitfall thường gặp khi viết conditional enqueue lần đầu.

  • is_singular() trước khi global $post sẵn: hook quá sớm gây check fail — luôn dùng trong wp_enqueue_scripts, không dùng trong init.
  • Quên fallback cho 404 và search: 404 page không match condition nào → trắng style — luôn có style baseline (tokens + layout) load always.
  • has_block() trên reusable block: reusable block không count trong has_block() ở post chứa nó — phải dùng parse_blocks() manual nếu cần check.

Defer, async, và control script loading

Mặc định <script src> render-blocking — browser dừng parse HTML để download và execute script. Defer hoặc async cải thiện LCP 200-500ms cho theme nhiều JavaScript.

WordPress 6.3+ hỗ trợ native qua wp_enqueue_script_module() nhưng API mới chưa phổ thông. Pattern compat WP 5.x+ dùng filter script_loader_tag vẫn an toàn nhất cho 2026.

Pattern filter script_loader_tag

Đoạn code dưới minh hoạ filter script_loader_tag để add attribute defer hoặc async cho handle cụ thể. Pattern này compat từ WP 5.0 trở lên, không cần lo version.

add_filter('script_loader_tag', function ($tag, $handle, $src) {
    $defer = ['w22-main', 'w22-form', 'w22-analytics'];
    $async = ['w22-third-party'];

    if (in_array($handle, $defer, true)) {
        return sprintf(
            '<script defer src="%s" id="%s-js"></script>',
            esc_url($src),
            esc_attr($handle)
        );
    }
    if (in_array($handle, $async, true)) {
        return sprintf(
            '<script async src="%s" id="%s-js"></script>',
            esc_url($src),
            esc_attr($handle)
        );
    }
    return $tag;
}, 10, 3);

Defer, async, hay không attribute — chọn cái nào

Ba lựa chọn cho mỗi script khác nhau cơ bản về thứ tự download và execute. Chọn sai làm vỡ thứ tự dependency hoặc làm chậm trang.

  • Defer: download song song với HTML parse, execute sau khi parse xong, giữ thứ tự script — dùng cho script DOM-dependent như slider, form validate.
  • Async: download và execute song song, không giữ thứ tự — dùng cho analytics, tracking, third-party độc lập không phụ thuộc script khác.
  • Không attribute (render-blocking): chỉ giữ cho script critical inline như dark mode toggle cần chạy trước paint — mọi script khác nên có defer hoặc async.

Sai lầm khi áp async cho script có dependency

Async không giữ thứ tự execute — nếu script-B depend script-A mà cả hai async, B có thể chạy trước A gây undefined error. Quy tắc đơn giản: script có dependency dùng defer, script độc lập mới dùng async.

Inline data — wp_localize_script vs wp_add_inline_script

Pass biến PHP sang JavaScript có 2 cách official trong WordPress. Mỗi cách phù hợp use case khác nhau, dùng sai gây sai sót pattern dài hạn.

Cấm tuyệt đối: echo trực tiếp <script>...</script> trong header.php — không qua escape, không control loading order, dễ bị XSS nếu inject biến từ DB.

wp_localize_script — pass data array

Method legacy nhưng vẫn được khuyến khích cho data scalar và array đơn giản. Pattern phổ biến nhất là pass ajaxUrl, nonce, REST endpoint, và i18n string.

wp_localize_script('w22-main', 'web22Data', [
    'ajaxUrl' => admin_url('admin-ajax.php'),
    'nonce'   => wp_create_nonce('w22_nonce'),
    'restUrl' => esc_url_raw(rest_url()),
    'i18n'    => [
        'sending' => __('Đang gửi...', 'web22-2026'),
        'sent'    => __('Đã gửi thành công', 'web22-2026'),
    ],
]);
// JS access: web22Data.ajaxUrl, web22Data.i18n.sending

wp_add_inline_script — JS code arbitrary

Method linh hoạt hơn, cho phép inject JS expression bất kỳ chứ không chỉ data object. Dùng cho feature flag, computation, condition trước khi script main chạy.

// Inject feature flag check trước w22-main
wp_add_inline_script(
    'w22-main',
    'window.W22_FLAGS = ' . wp_json_encode([
        'darkMode' => true,
        'newCart'  => false,
    ]) . ';',
    'before'
);

Decision matrix khi nào dùng cái nào

Hai method phục vụ use case khác nhau, không thay thế lẫn nhau. Bảng decision dưới giúp em chọn nhanh trong 30 giây.

  • Simple data structure: dùng wp_localize_script — pass config, URL, i18n, nonce — code rõ ràng nhất.
  • JS code có condition hoặc computation: dùng wp_add_inline_script — vd window flag check, polyfill conditional load.
  • Position before vs after handle chính: wp_add_inline_script nhận parameter thứ 3 — flag setup trước, override hook sau handle chính.

Dequeue plugin asset không cần cho trang hiện tại

Plugin thường enqueue CSS và JS toàn site dù chỉ một page dùng. Contact Form 7 là ví dụ kinh điển — enqueue CSS và JS trên mọi page dù form chỉ ở /lien-he/.

Dequeue tiết kiệm 50-150KB asset không cần. Pattern đơn giản: hook vào wp_enqueue_scripts priority cao (chạy sau plugin), conditional check trang, gọi wp_dequeue_*.

Pattern dequeue conditional

Đoạn code dưới minh hoạ dequeue Contact Form 7 trên mọi page trừ /lien-he/. Priority 100 đảm bảo chạy sau plugin enqueue ở priority default 10.

add_action('wp_enqueue_scripts', function () {
    if (!is_page('lien-he')) {
        wp_dequeue_style('contact-form-7');
        wp_dequeue_script('contact-form-7');
    }
}, 100);

// Dequeue Yoast SEO emoji nếu không dùng
add_action('init', function () {
    remove_action('wp_head', 'print_emoji_detection_script', 7);
    remove_action('wp_print_styles', 'print_emoji_styles');
});

Cách identify handle plugin để dequeue

Trước khi dequeue, em cần biết handle plugin dùng. Có 2 cách identify nhanh, không cần đọc source plugin.

  • View page source HTML: tìm id="xxx-css"id="xxx-js" trong output — phần trước “-css” hoặc “-js” chính là handle.
  • Plugin Asset CleanUp: visualize list tất cả handle đang load với checkbox dequeue UI — phù hợp dev không thoải mái với code.
  • Query Monitor: tab Scripts & Styles list mọi handle với src, deps, version — hữu ích debug dependency chain.

Cẩn thận dequeue gây vỡ feature

Dequeue script mà plugin cần dẫn tới feature stop work âm thầm. Test plugin sau mỗi lần dequeue trên staging trước khi push production — đặc biệt WooCommerce với 30+ handle phụ thuộc lẫn nhau.

Versioning và cache busting bền vững

Browser cache CSS và JS theo URL. Update file mà không đổi URL đồng nghĩa user vẫn thấy version cũ — phá UX với những bug đã fix nhưng client vẫn báo lại.

Ba strategy versioning dưới có trade-off khác nhau. Chọn theo workflow deploy cụ thể của em — không có chiến lược nào tốt nhất cho mọi project.

Version constant — pattern phổ biến nhất

Tham số thứ 4 của wp_enqueue_* là version string. Pattern chuẩn dùng constant W22_VERSION định nghĩa đầu functions.php, bump version mỗi lần deploy.

  • Đơn giản nhất: chỉ cần đổi constant — mọi asset auto bust cache đồng loạt.
  • Phù hợp deploy thủ công: dev nhỏ deploy 1-2 lần/tuần — workflow này không cần build tool.
  • Nhược điểm: bust cache toàn bộ asset cùng lúc dù chỉ sửa 1 file — user phải re-download mọi thứ.

filemtime() — auto version theo timestamp

Tham số version dùng filemtime() để auto-version theo timestamp file modified. Mỗi lần edit file là cache miss, không cần bump constant thủ công.

wp_enqueue_script(
    'w22-main',
    get_template_directory_uri() . '/assets/js/main.js',
    [],
    filemtime(get_template_directory() . '/assets/js/main.js'),
    true
);

Hash content — production build với webpack/Vite

Build tool production output filename có hash content như main.abc123.css. Không cần param ?ver= — URL đã unique theo hash.

Set version = null để WordPress không thêm query string.

Câu hỏi thường gặp

Có nên dùng wp_print_styles thay wp_enqueue_style?

Không. wp_print_styles là internal function trong WordPress core — dùng sai context dẫn tới duplicate render hoặc style miss.

Luôn dùng wp_enqueue_style trong hook wp_enqueue_scripts. WordPress sẽ tự gọi wp_print_styles đúng lúc trong lifecycle — em không cần can thiệp.

Plugin SEO báo render-blocking cho theme.css — fix thế nào?

Inline critical CSS (above-the-fold) trong <head> qua wp_add_inline_style, defer phần còn lại. Critical CSS thường chỉ 4-8KB cho theme thường.

Hoặc dùng plugin WP Rocket hay Autoptimize để auto-generate critical CSS. Theme web22-2026 inline tokens và layout khoảng 6KB, defer phần còn lại — đạt Lighthouse 95+ mobile.

Có nên enqueue jQuery cho theme custom?

Không trừ khi dùng plugin yêu cầu jQuery như Contact Form 7 hay WooCommerce. Vanilla JS đủ cho 95% UI theme — fetch API, querySelector, IntersectionObserver.

Tiết kiệm 30KB jQuery và tránh conflict version giữa các plugin. Native Web API phổ biến rộng từ 2018 trở đi — không có lý do gì để cõng jQuery cho theme mới 2026.

wp_enqueue_script trong shortcode hoặc block render có chạy không?

Có nhưng không phải trong <head> vì hook đã pass thời điểm render shortcode. Asset sẽ insert vào footer thay vì head.

Pattern chuẩn: register asset trong hook wp_enqueue_scripts, sau đó enqueue trong shortcode hoặc block render khi có nhu cầu. WordPress tự động print trong wp_footer.

Enqueue CSS từ CDN có OK không?

OK nhưng có rủi ro single-point-of-failure — CDN down đồng nghĩa site không có style, UX vỡ nặng. Tốt hơn là self-host CSS critical, chỉ dùng CDN cho asset không critical như font icon.

Web22-2026 self-host toàn bộ CSS để pass Core Web Vitals và tránh rủi ro CDN. Việt Nam còn vấn đề latency với CDN US/EU — self-host hosting VN thường nhanh hơn rõ rệt.

Tài nguyên và bước tiếp theo

Enqueue đúng pattern là baseline kỹ thuật cho mọi theme WordPress production. Sau khi nắm 6 pattern trên, mở rộng sang các topic kỹ thuật khác để hoàn thiện theme.

Cần đội Web22 audit theme và tối ưu enqueue chuẩn 2026 cho dự án WordPress của bạn? Thi công WordPress theo yêu cầu — block theme 2026 — báo cáo conditional load, dequeue plugin asset, defer strategy trong 5-7 ngày làm việc.