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
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
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 tronginit. - 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ùngparse_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_scriptnhậ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"và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.
- functions.php best practice — 7 pattern theme WordPress scale — cấu trúc require_once, hook priority, security baseline.
- Walker_Nav_Menu tùy biến — build mega menu WordPress — custom markup nav với 4 method bắt buộc.
- WooCommerce theme support — 3 cấp độ + override template — declare support đúng để tránh warning shop.
- Translate theme WordPress — i18n + .pot/.po/.mo workflow — đa ngữ cho theme submit WordPress.org.
- Dịch vụ thiết kế website WordPress chuyên nghiệp — gói thi công theme custom turnkey.
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.


