theme-elementary

Live reload and block refresh

This document explains how live reload and block-editor hot module replacement work in the theme’s development workflow, and how to configure them — including for HTTPS local environments.

Overview

The theme has two development servers. They complement the WordPress site; you continue browsing the site’s normal URL. Running npm start runs both in parallel:

Watcher Server Purpose
npm run start:assets BrowserSync, port 3001 Live reload for the frontend via snippet mode; your site URL stays unchanged.
npm run start:blocks webpack-dev-server / Fast Refresh, port 8887 by default (configurable via BLOCKS_DEV_SERVER_PORT) Hot module replacement for block editor React components; requires block sources. Block state is preserved across updates — no full page reload needed.

For BrowserSync:

For block editor HMR, JS/JSX changes to block components hot-swap in the editor instantly.

Quick Start

npm start

Webpack starts watching for file changes and BrowserSync starts on port 3001. Open your local site and edits will reflect automatically.

Requirements

The BrowserSync client script is only enqueued when WP_ENVIRONMENT_TYPE is set to local. Add this to your wp-config.php if it isn’t already:

define( 'WP_ENVIRONMENT_TYPE', 'local' );

For block editor HMR (Fast Refresh), also add:

define( 'SCRIPT_DEBUG', true );

Without SCRIPT_DEBUG, WordPress does not support Fast Refresh.

How It Works

Theme assets (start:assets + BrowserSync)

  1. start:assets runs the asset watcher in watch mode using webpack.config.js.
  2. When a file changes, webpack rebuilds the affected assets in assets/build/.
  3. BrowserSync detects the change and notifies the browser via the client script.
  4. CSS changes are injected in-place. Everything else triggers a full reload.

Blocks (start:blocks + Fast Refresh)

  1. start:blocks starts a webpack dev server, using webpack.blocks.config.js.
  2. JS/JSX changes to block components hot-swap in the editor without a full reload; block state is preserved.

webpack.blocks.config.js is a thin wrapper over @wordpress/scripts’ default config. It exists only to strip the devServer.proxy option: webpack-dev-server v5 (pinned via the overrides block in package.json) requires proxy to be an array, while wp-scripts still emits the v4 object form, which v5 rejects with options.proxy should be an array. The wrapper also sets the dev-server port from BLOCKS_DEV_SERVER_PORT.

BrowserSync watches the following:

The client script is enqueued by PHP from {scheme}://{host}:3001/browser-sync/browser-sync-client.js. The scheme (http or https) and host are derived automatically from the WordPress site URL using is_ssl() and home_url().

BrowserSync is only added to the scripts webpack config. Adding it to all three configs (scripts, styles, moduleScripts) would start three BrowserSync instances on the same port.

Configuration

Copy .env.local.example to .env.local and set your local site hostname:

WP_HOST=yoursite.local

WP_HOST is your local site’s hostname (without protocol or port). Set it to match your local hostname exactly.

.env.local is gitignored.

Multiple sites / custom URL

If port 3001 is already taken (e.g. two local sites running at once), set a different port in .env.local:

BS_PORT=3002

Then define the matching constant in wp-config.php so PHP enqueues the client from the right URL:

define( 'ELEMENTARY_THEME_BROWSER_SYNC_URL', 'https://yoursite.local:3002/browser-sync/browser-sync-client.js' );

ELEMENTARY_THEME_BROWSER_SYNC_URL overrides the auto-detected URL entirely, so it also works for remote setups (ddev, reverse proxy) where the BrowserSync server is on a different host or IP.

Block dev server port

The block Fast Refresh dev server runs on port 8887 by default. If that port is already in use (e.g. two local sites running start:blocks at once), set a different port in .env.local:

BLOCKS_DEV_SERVER_PORT=8889

webpack.blocks.config.js reads this value and applies it to the dev server. No matching wp-config.php constant is needed — the editor loads block scripts from the dev server directly.

HTTPS

If your local site runs on HTTPS, also add the SSL cert paths:

WP_SSL_KEY=/path/to/yoursite.local.key
WP_SSL_CERT=/path/to/yoursite.local.crt

This is required to avoid mixed content errors — the BrowserSync client script on port 3001 must also be served over HTTPS. Since SSL certs are domain-based, the same cert your local site uses also covers port 3001.

Finding cert paths in LocalWP (macOS):

~/Library/Application Support/Local/run/router/nginx/certs/<domain>.key
~/Library/Application Support/Local/run/router/nginx/certs/<domain>.crt

Advanced

Enabling / disabling HMR

HMR (BrowserSync live reload) is controlled by a single master switch in .env.local, honoured by both the build (BrowserSync server) and PHP (client enqueue):

ENABLE_HMR=false

Default is on — the key only needs setting to turn HMR off. Off values are 0, false, no, and off (case-insensitive). With it off, npm start skips the BrowserSync server entirely and PHP skips the client, so there is no live reload and no console noise from a client pointing at a server that isn’t running. The browser-sync dev dependencies stay installed, so flipping it back on needs no reinstall.

You can also toggle it from npm run init (manage mode → Toggle features → HMR), which just flips ENABLE_HMR in .env.local for you. Since .env.local is gitignored, this is a per-developer local setting.

Disabling the BrowserSync client only

To keep the BrowserSync server running but stop PHP from enqueuing its client (e.g. when working purely in the block editor), set this in .env.local:

DISABLE_BS=true

This prevents PHP from enqueuing the BrowserSync client script. The BrowserSync server still starts (webpack still runs it), but the browser won’t connect to it. For a full off switch (server included), use ENABLE_HMR=false above.

Overriding the BrowserSync client URL

By default, PHP constructs the client URL from the site’s scheme and host:

{scheme}://{host}:3001/browser-sync/browser-sync-client.js

To override it entirely — for a non-standard port, a remote dev server, or a reverse proxy setup — define this constant in wp-config.php:

define( 'ELEMENTARY_THEME_BROWSER_SYNC_URL', 'https://yoursite.local:3002/browser-sync/browser-sync-client.js' );

This takes precedence over the auto-detected URL.

Troubleshooting

Known Limitations

BrowserSync port: BrowserSync requires its own port (3001) separate from your local site. Snippet mode keeps the site URL unchanged — proxy mode would change the URL and break WordPress redirects and cookie domains.

WDS host validation: WDS runs on localhost:8887. For custom local hostnames (e.g. yoursite.local), allowedHosts: 'all' is set in the webpack devServer config so the HMR WebSocket connection is accepted.