Skip to content

Hyvä Response Minification and Merging

The hyva-themes/magento2-minification module provides HTML, inline CSS, and inline JavaScript response minification for Hyvä themes via a Node.js daemon.

It minifies full-page HTML responses before they leave PHP (and before Varnish caches them), using @swc/html — a native Rust-based minifier that handles HTML, JavaScript, and CSS in a single pass with Alpine.js-safe settings.

Performance Trade-off: Slower Uncached Responses

Important: Enabling minification increases response time for uncached pages. Every cache-miss response is sent to the minifier daemon before being returned to the browser, which adds roughly 70–120 ms of latency per uncached request (see the Daemon Throughput benchmark below).

Cache hits — both Varnish full-page cache and the Magento block_html cache — are not affected; they serve the already-minified response directly.
Enabling only JS merging does not cause this performance impact.

APDEX Impact

Because uncached responses are slower, your APDEX score will drop slightly after enabling minification. The size of the drop depends on your cache hit rate and your current APDEX target (T) value — the lower the cache hit rate, the larger the impact.

Recommendation: after enabling minification, raise your APDEX target time by approximately the daemon's typical added latency (around 100 ms) so the score reflects the new baseline response time rather than flagging every uncached page as "tolerating" or "frustrating". For example, if your current APDEX T is 500 ms, consider raising it to 600 ms.

The trade-off is intentional: minification shifts cost from many cached responses (smaller payloads, faster downloads, less bandwidth, lower CSP header size) to a one-time cost on cache misses. On sites with a healthy Varnish hit rate, the net user-perceived performance improves despite the slower cache-miss path.

Strict CSP Compatibility

The hyva-themes/magento2-minification module is compatible with both hyva-themes/magento2-default-theme and hyva-themes/magento2-default-theme-csp (strict Content-Security-Policy). SHA-256 hashes for inline scripts are automatically computed from the minified content before CSP headers are written, so browsers receive matching hashes for the minified scripts.

This works transparently for all page types, including pages with Varnish ESI blocks and blocks served from the block_html cache.

Requirements

  • Magento 2.4.x with Hyvä Theme ^1.3.11
  • Node.js >= 20
  • PHP 8.1+

Installation

composer require hyva-themes/magento2-minification
bin/magento module:enable Hyva_Minification
bin/magento setup:upgrade

Starting the Daemon

cd vendor/hyva-themes/magento2-minification/node
npm install
npm run minifier-daemon

The daemon listens on 127.0.0.1:3100 by default.

Security: The daemon has no authentication. Keep it bound to 127.0.0.1 (the default) and do not set MINIFIER_HOST to 0.0.0.0 or a public interface. In containerized environments, ensure the daemon port is not exposed outside the pod or host network.

Environment Variables

Variable Default Description
MINIFIER_PORT 3100 TCP port
MINIFIER_HOST 127.0.0.1 Bind address
MINIFIER_SOCKET (empty) Unix socket path (overrides TCP when set)
MALLOC_ARENA_MAX 2 (set by Magento-managed start) Caps glibc malloc arenas — see below

Memory: MALLOC_ARENA_MAX

When the daemon is started by Magento (cron, on-demand, or admin button), it is launched with MALLOC_ARENA_MAX=2 in its environment. This caps glibc's per-thread allocator pools at 2 instead of the default 8 × CPU cores.

The HTML/JS minifier produces many small short-lived allocations (AST nodes, attribute strings, mangler symbol tables). glibc rarely returns freed memory to the OS — it keeps it in per-arena free-lists. On multi-core hosts this can push the daemon's RSS into the tens of GB even though the actual working set is small. Capping arenas bounds the fragmentation overhead, typically reducing long-term RSS by an order of magnitude.

Trade-off: threads now share arenas, so malloc/free can contend on an arena lock. The daemon is single-threaded JS with a small libuv worker pool, so contention is negligible in practice.

The variable is harmless on platforms that don't use glibc (musl/Alpine, macOS, BSD) — those allocators ignore it. If you run the daemon under a system service manager (systemd, supervisor, etc.), add MALLOC_ARENA_MAX=2 to the unit's environment as shown in the systemd example above.

Unix Socket Example

MINIFIER_SOCKET=/tmp/hyva-minifier.sock npm run minifier-daemon

Running in Production

Use a process manager to keep the daemon alive. Example systemd unit:

[Unit]
Description=Hyvä Minifier Daemon
After=network.target

[Service]
Type=simple
Environment=MALLOC_ARENA_MAX=2
WorkingDirectory=/var/www/html/vendor/hyva-themes/magento2-minification/node
ExecStart=/usr/bin/node daemon.mjs
Restart=always
User=www-data

[Install]
WantedBy=multi-user.target

Daemon Management

On environments where a system service is not available (e.g. Adobe Commerce Cloud), the daemon can be managed automatically. Navigate to Stores > Configuration > Hyvä Themes > System > Response Minification > Daemon Management and select one of:

Mode Description
Off (default) The daemon must be started manually or via a process manager (systemd, supervisor, etc.)
Cron A watchdog cron job runs every minute — if the daemon is not responding, it starts a new instance
Start on-demand The daemon is started automatically on the first request that needs it. A file lock prevents concurrent requests from spawning multiple processes, and a failure counter stops retrying after 30 consecutive failures (auto-resets after 1 hour or when the daemon becomes healthy)

Or using the CLI:

bin/magento config:set hyva_minification/general/daemon_management cron
bin/magento config:set hyva_minification/general/daemon_management on_demand
bin/magento config:set hyva_minification/general/daemon_management off

Legacy env.php setting: The hyva_minification/cron_daemon_manager_enabled setting in app/etc/env.php is still supported. When present and truthy, it takes precedence over the admin panel setting and locks the dropdown to Cron. Remove the env.php setting to manage this option in the admin panel instead.

When minification is enabled, the admin panel shows a daemon status panel with:

  • Whether the daemon is running
  • Current daemon management mode
  • PID and last start time (when available)
  • Daemon log file size
  • A Start, Stop, or Restart button depending on the current state

To restart the daemon without server access (e.g. after a deploy), click the restart button in the admin panel, or create a restart signal file manually:

touch var/hyva_minifier_shutdown.flag

The daemon checks for this file every 5 seconds. When found, it shuts down cleanly. If daemon management is set to Cron or Start on-demand, a new instance is started automatically.

File permissions (Cron mode): The cron daemon manager requires that both the web server user and the cron process can read and write the same files in var/ (PID file, log, restart flag). This is the case on Adobe Commerce Cloud out of the box. For on-premise deployments, ensure both processes run as the same OS user, or belong to a shared group with a consistent umask so that files created by one process remain writable by the other.

See the Adobe Commerce documentation for details: - File ownership and permissions (overview) - Configure file ownership and permissions

Configuration

Daemon Connection (env.php)

The daemon address is configured in app/etc/env.php (not in the admin panel) so that only users with server access can change where the PHP process connects.

The defaults work out of the box for a standard single-host setup (tcp://127.0.0.1:3100). Override them when the daemon runs on a different host or uses a Unix socket.

Using [n98-magerun](https://github.com/netz98/n98-magerun2):

# TCP (default) — set host and port
n98-magerun config:env:set hyva_minification/transport tcp
n98-magerun config:env:set hyva_minification/tcp_host 127.0.0.1
n98-magerun config:env:set hyva_minification/tcp_port 3100

# Unix socket — set transport and socket path
n98-magerun config:env:set hyva_minification/transport unix_socket
n98-magerun config:env:set hyva_minification/socket_path /tmp/hyva-minifier.sock

This produces the following structure in app/etc/env.php:

'hyva_minification' => [
    'transport' => 'tcp',
    'tcp_host' => '127.0.0.1',
    'tcp_port' => '3100',
    // 'socket_path' => '/tmp/hyva-minifier.sock',
],
Key Default Description
transport tcp tcp or unix_socket
tcp_host 127.0.0.1 Daemon TCP host
tcp_port 3100 Daemon TCP port
socket_path /tmp/hyva-minifier.sock Unix socket path (used when transport is unix_socket)
cron_daemon_manager_enabled false Legacy. When true, locks daemon management to Cron (see above)

Admin Panel

Navigate to Stores > Configuration > Hyvä Themes > System > Response Minification to configure:

  • Enable — master toggle (store-scoped)
  • Daemon Management — Off, Cron, or Start on-demand (global scope; see above)
  • Merge Inline JavaScript — merge all inline scripts into a single <script> tag (see below)
  • Connection Timeout — how long to wait for a connection before falling back (default: 100ms)
  • Read Timeout — how long to wait for the minified response (default: 2500ms)
  • Shell Fallback (Developer Mode) — fall back to shelling out to node when the daemon is unreachable (see below)

Merge Inline JavaScript

When enabled, all inline <script> tags are merged into a single <script> tag placed just before </body>. This reduces the number of SHA-256 hashes in the Content-Security-Policy header from one per inline script to just one for the merged result.

This is especially useful with hyva-themes/magento2-default-theme-csp (strict CSP), where a typical page may contain 20+ inline scripts, each requiring its own hash in the CSP header. Merging them into one script dramatically reduces header size, which benefits mobile performance and avoids header-size limits in some proxies and firewalls.

Key details:

  • Cacheable pages only — scripts are only merged on pages that are cacheable in the full page cache (FPC). Uncacheable pages (e.g. checkout, customer account) are left as-is, because they don't shas to authorize inline scripts but only a single nonce.
  • Works independently of minification — the merge can be enabled without the Node.js daemon; it runs entirely in PHP
  • Only merges JavaScript — scripts with type="text/javascript" or no type attribute are merged. Non-JS types (module, application/json, speculationrules, etc.) are left untouched
  • Varnish ESI compatible — scripts inside ESI blocks are captured and included in the merge
  • Disabled by default — enable it in the admin panel or via CLI:
    bin/magento config:set hyva_minification/general/merge_inline_js 1
    

Behaviour

  • Only minifies text/html frontend responses on Hyvä-themed storefronts
  • Admin panel and Luman theme responses are never minified
  • If the daemon is unreachable or returns an error, the original unminified response is served and a warning is logged
  • If the minified output is not smaller than the original, the original is served
  • Varnish caches the minified response on cache miss; subsequent cache hits serve it directly

Developer Mode Features

In developer mode:

  • An X-Hyva-Minified: 1 response header is added when minification was applied
  • Append ?hyva_minification=false to any URL to bypass minification for that request

Shell Fallback

In developer mode, you can enable a shell fallback that minifies responses by shelling out to node directly, without requiring the daemon to be running. This is slower than the daemon (each request spawns a new Node process) but convenient for quick testing of whether a module works with minification or merging.

Enable it in the admin panel under Stores > Configuration > Hyvä Themes > System > Response Minification > Shell Fallback (Developer Mode), or via CLI:

bin/magento config:set hyva_minification/general/dev_shell_fallback_enabled 1

The fallback only activates when the daemon is unreachable and Magento is in developer mode. If the daemon is running, it is always preferred.

Custom Node Binary Path

If node is not on the default PATH (e.g. nvm-managed Node, or a non-standard container layout), configure the path in app/etc/env.php.

The recommended location is the shared Hyvä key, so all Hyvä packages can use it:

'hyva' => [
    'node_binary' => '/usr/local/bin/node',
],

For backward compatibility, the minification-specific key is also supported and takes precedence:

'hyva_minification' => [
    'node_binary' => '/usr/local/bin/node',
],

Lookup order: hyva_minification/node_binaryhyva/node_binarynode (from $PATH).

Benchmark: HTML Size Reduction

Measured on Magento 2.4.8-p3 with Hyvä Theme and sample data:

Page Minified Unminified Savings %
Homepage 334,419 538,823 204,404 37.9%
PLP (Category) 410,999 758,874 347,875 45.8%
PDP (Configurable) 302,750 519,881 217,131 41.8%
Login 158,395 264,022 105,627 40.0%
Registration 187,997 322,330 134,333 41.7%
Customer Dashboard 198,347 349,158 150,811 43.2%
Order History 194,409 342,948 148,539 43.3%
Wishlist 175,188 294,121 118,933 40.4%
Cart 260,330 465,930 205,600 44.1%
Checkout 294,501 590,686 296,185 50.1%
Min Max Average Median
Savings 37.9% 50.1% 42.8% 42.5%

Benchmark: Daemon Throughput

Measured with 20 concurrent requests, ~500 KB HTML payload, 60-second run:

Metric Value
Requests/sec 167.28
Avg latency 120 ms
P50 latency 120 ms
P95 latency 124 ms
P99 latency 128 ms
Errors 0
Input size 505 KB
Output size 383 KB
Size savings 24.1%

The daemon is single-threaded (Node.js event loop). Minification is CPU-bound, so requests are serialized under concurrency. The native Rust-based @swc/html minifier processes each request in ~70–120 ms. For production workloads behind Varnish, the daemon only processes cache misses, so throughput is rarely a bottleneck.

This added per-request latency is the source of the slower uncached-response time and APDEX drop noted in Performance Trade-off: Slower Uncached Responses.

Verifying It Works

# Health check
curl http://127.0.0.1:3100/health

# Test minification directly
curl -s -X POST -H "Content-Type: text/html" \
  -d '<html><body>  <p>hello</p>  </body></html>' \
  http://127.0.0.1:3100/

License

This package is licensed under the Open Software License (OSL 3.0).

  • Copyright: Copyright © 2020-present Hyvä Themes. All rights reserved.
  • License Text (OSL 3.0): The full text of the OSL 3.0 license can be found in the LICENSE.txt file within this package, and is also available online at http://opensource.org/licenses/osl-3.0.php.