Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Kévin Dunglas <kevin@dunglas.fr>
5.0 KiB
Hot Reload
FrankenPHP includes a built-in hot reload feature designed to vastly improve the developer experience.
This feature provides a workflow similar to Hot Module Replacement (HMR) found in modern JavaScript tooling (like Vite or webpack). Instead of manually refreshing the browser after every file change (PHP code, templates, JavaScript and CSS files...), FrankenPHP updates the content in real-time.
Hot Reload natively works with WordPress, Laravel, Symfony, and any other PHP application or framework.
When enabled, FrankenPHP watches your current working directory for filesystem changes. When a file is modified, it pushes a Mercure update to the browser.
Depending on your setup, the browser will either:
- Morph the DOM (preserving scroll position and input state) if Idiomorph is loaded.
- Reload the page (standard live reload) if Idiomorph is not present.
Configuration
To enable hot reloading, enable Mercure, then add the hot_reload sub-directive to the php_server directive in your Caddyfile.
Warning
This feature is intended for development environments only. Do not enable
hot_reloadin production, as watching the filesystem incurs performance overhead and exposes internal endpoints.
localhost
mercure {
anonymous
}
root public/
php_server {
hot_reload
}
By default, FrankenPHP will watch all files in the current working directory matching this glob pattern: ./**/*.{css,env,gif,htm,html,jpg,jpeg,js,mjs,php,png,svg,twig,webp,xml,yaml,yml}
It's possible to explicitly set the files to watch using the glob syntax:
localhost
mercure {
anonymous
}
root public/
php_server {
hot_reload src/**/*{.php,.js} config/**/*.yaml
}
Use the long form to specify the Mercure topic to use as well as which directories or files to watch by providing paths to the hot_reload option:
localhost
mercure {
anonymous
}
root public/
php_server {
hot_reload {
topic hot-reload-topic
watch src/**/*.php
watch assets/**/*.{ts,json}
watch templates/
watch public/css/
}
}
Client-Side Integration
While the server detects changes, the browser needs to subscribe to these events to update the page.
FrankenPHP exposes the Mercure Hub URL to use for subscribing to file changes via the $_SERVER['FRANKENPHP_HOT_RELOAD'] environment variable.
A convenience JavaScript library, frankenphp-hot-reload, is also available to handle the client-side logic. To use it, add the following to your main layout:
<!DOCTYPE html>
<title>FrankenPHP Hot Reload</title>
<?php if (isset($_SERVER['FRANKENPHP_HOT_RELOAD'])): ?>
<meta name="frankenphp-hot-reload:url" content="<?=$_SERVER['FRANKENPHP_HOT_RELOAD']?>">
<script src="https://cdn.jsdelivr.net/npm/idiomorph"></script>
<script src="https://cdn.jsdelivr.net/npm/frankenphp-hot-reload/+esm" type="module"></script>
<?php endif ?>
The library will automatically subscribe to the Mercure hub, fetch the current URL in the background when a file change is detected and morph the DOM. It is available as a npm package and on GitHub.
Alternatively, you can implement your own client-side logic by subscribing directly to the Mercure hub using the EventSource native JavaScript class.
Worker Mode
If you are running your application in Worker Mode, your application script remains in memory. This means changes to your PHP code will not be reflected immediately, even if the browser reloads.
For the best developer experience, you should combine hot_reload with the watch sub-directive in the worker directive.
hot_reload: refreshes the browser when files changeworker.watch: restarts the worker when files change
localhost
mercure {
anonymous
}
root public/
php_server {
hot_reload
worker {
file /path/to/my_worker.php
watch
}
}
How it works
- Watch: FrankenPHP monitors the filesystem for modifications using the
e-dant/watcherlibrary under the hood (we contributed the Go binding). - Restart (Worker Mode): if
watchis enabled in the worker config, the PHP worker is restarted to load the new code. - Push: a JSON payload containing the list of changed files is sent to the built-in Mercure hub.
- Receive: The browser, listening via the JavaScript library, receives the Mercure event.
- Update:
- If Idiomorph is detected, it fetches the updated content and morphs the current HTML to match the new state, applying changes instantly without losing state.
- Otherwise,
window.location.reload()is called to refresh the page.
