Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Hot Reload Bundle Laravel Package

nowo-tech/hot-reload-bundle

Symfony bundle that brings hot reloading to your development workflow. It automatically refreshes the browser when templates, assets, or PHP files change, so you can iterate faster and skip manual reloads. Lightweight and easy to install.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and register (5 minutes)

composer require --dev nowo-tech/hot-reload-bundle

There is no Flex recipe and zero dependents in the wild, so register the bundle manually in config/bundles.php (dev/test only is fine — it is a dev convenience):

Nowo\HotReloadBundle\HotReloadBundle::class => ['dev' => true, 'test' => true],

Turn it on where FrankenPHP runs. The bundle's contract is "inject the client when FRANKENPHP_HOT_RELOAD is available", so the switch is an environment variable on the PHP process, not a Symfony config flag:

# compose.yaml — the FrankenPHP service only
services:
  php:
    image: dunglas/frankenphp
    environment:
      FRANKENPHP_HOT_RELOAD: '1'
      MERCURE_URL: 'http://localhost:4040/.well-known/mercure'
    volumes:
      - .:/app

Look here first, in this order:

  1. src/DependencyInjection/Configuration.php — the real root key (likely nowo_tech_hot_reload) and which knobs exist (enabled flag, Mercure URL, topic, injection target). php bin/console config:dump-reference <root_key> after installing.
  2. The kernel.response listener/subscriber — this is the whole feature. Read exactly what it checks before injecting.
  3. src/Resources/ — the template/snippet it appends and where it appends it (</body>, </head>).

First use case: start FrankenPHP in worker mode with the watcher, open a page, edit a Twig template, and confirm the browser reloads without a manual refresh. If nothing happens, curl -s http://localhost/ | grep -i -E 'mercure|hot.?reload' tells you immediately whether the injection fired — that one command splits the problem in half.

Implementation Patterns

Treat it as dev-only infrastructure, not application code. The cleanest pattern is to leave every template untouched and let the bundle do the injection. Resist the temptation to also add {% if app.debug %} guards around it — you then have two sources of truth and the env var becomes decorative.

Gate it at the container level, per environment. Put FRANKENPHP_HOT_RELOAD in your local compose.override.yaml (or .env.local) and never in the prod image. Because the bundle keys off process env rather than kernel.environment, a stray variable in a shared .env will happily inject the client in staging.

Keep it out of the asset pipeline. The hot reload client comes from FrankenPHP's Mercure hub, not from AssetMapper/Encore. Do not try to import it, version it, or add it to importmap.php — you will fight the bundle and lose.

Verify both states in CI. A cheap, high-value smoke test:

// no FRANKENPHP_HOT_RELOAD → no injected script
$client->request('GET', '/');
self::assertStringNotContainsString('mercure', $client->getResponse()->getContent());

Then run the same assertion inverted with the env var set via putenv() in the test — if the bundle reads env at container build time you will discover that here rather than in a demo.

Coexist with other response-level tooling. If you have listeners adding the Web Debug Toolbar, CSP nonces, or analytics snippets, confirm ordering. A listener that appends before </body> will land adjacent to the toolbar; check which one wins in the rendered output.

For live-components/Turbo pages, a full page reload is a blunt instrument. If the bundle exposes the reload trigger as a JS event or a configurable snippet, hook into it; otherwise scope hot reload to the pages you are actively editing (e.g. by decorating the listener and short-circuiting on non-/_fragment or non-app routes).

Gotchas and Tips

  • "Available" is not the same as "truthy". Whether FRANKENPHP_HOT_RELOAD=0 or an empty string disables injection depends on the check in the listener. Read that line before trusting it; if it uses getenv() !== false, an empty value may still enable it.
  • Env visibility under FrankenPHP. With clear_env in php-fpm-style configs, or when variables_order omits E, getenv() can return false even though Docker sets the variable. $_SERVER/$_ENV are more reliable in containerised setups — compare with what the bundle actually reads.
  • Config cache. Symfony's %env()% is resolved at container compile time unless the env processor is marked runtime. If you enable hot reload and see nothing, run bin/console cache:clear before assuming a bug — then check whether a container rebuild is needed.
  • Inline vs. external script. If the bundle emits an inline <script>, any restrictive Content-Security-Policy (script-src 'self') silently kills it. Check the browser console; there is no PHP-side error. Prefer serving from an allowed origin, or add a nonce if the bundle supports it.
  • Mixed content. App on https:// + Mercure hub on http://localhost:4040 = a blocked EventSource with only a console warning. Terminate TLS consistently or point the hub URL at the same scheme.
  • Full-page reload destroys state. Scroll position, open modals, and half-filled forms vanish on every save. Keep hot reload on while actively editing templates, and disable it while doing form-heavy manual testing.
  • Injection by string replacement is fragile. If the implementation does str_replace('</body>', ...), responses without a closing </body> (streamed responses, JSON, fragments) are skipped — which is usually correct, but confirm StreamedResponse and BinaryFileResponse are excluded rather than accidentally stringified.
  • Classes are final by design in this bundle family. You probably cannot extend the listener. Override via service ID in services.yaml (replace the definition) or decorate through the DI container, not by inheritance.
  • Zero dependents, zero stars, single release in October 2026. Pin the exact version in composer.json, keep it in require-dev, and read the source before relying on it — it is small enough that the whole feature fits in a couple of files, so the cost of verifying behaviour is minutes, not hours.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels