Skip to content

Instantly share code, notes, and snippets.

@iamdadmin
Last active March 8, 2025 20:03
Show Gist options
  • Save iamdadmin/6cbe3ee511d4890b505ea40213e57bda to your computer and use it in GitHub Desktop.
Save iamdadmin/6cbe3ee511d4890b505ea40213e57bda to your computer and use it in GitHub Desktop.
Simple, if somewhat wasteful, automatic page refresh for Filament under Frankenphp

Introduction

FilamentPHP is a fantastic package which uses Livewire and cleverness to make quick and effective admin panels.

My development environment is Laravel Sail, with Octane configured to use Frankenphp as the application server.

The default theme and environment of Filament doesn't have HMR and doesn't run through Vite. Custom themes? Yep. This? Nope.

So I wanted a simple way of getting my page refreshed automatically.

How it works

As I mentioned above, I am using Frankenphp. The feature of Frankenphp I am relying on here is that the application is bootstrapped, once, with a set number of workers, and a cap on requests, when it's recycled and replaced with a fresh bootstrapped application, instead of bootstrapping each and every single page refresh. This means when the app boots, if I generate a timestamp and store it somewhere accessible, it won't change until the worker refreshes, no matter how many times the page refreshes.

Using an AppService provider, when the app boots a timestamp is generated and temporarily stored in a config key.

A client Javascript is added to the Filament renderHook::SCRIPTS_BEFORE to poll this every 3 seconds, store the timestamp in the browser, and refresh the page when either the poll notes the timestamp changed or on error. You can make that more or less frequent as suits your needs.

This also (unfortunately) means that a GET /app-boot appears in the STDOUT logs so I change the logging level to hide them as they aren't of use to me anyway. And further, if you use barryvdh/laravel-debugbar it's going to fill that with useless logs, unless you disable it for the route too, so I did.

Why do it this way? Why not use X or Y? Why did you put js/code directly in php against convention?

I am very, very early in the dev stages with this app. It doesn't have Redis, no Pusher, no Broadcasts/Notifications, no Events, no Server Sent Events. In following best practice I should have put the script in a blade or js file and sent it that way. But I wanted a one-page super-simple script to fit the job I need it to do now.

As I build up the app, I will either not need it anymore and remove it, or I will replace it with a solution which follows best practice. In the meantime, my pages refresh :)

How to deploy it

  1. Add a config key somewhere to your app, I placed it in config/app.php
    'boot' => '',
  1. Add the AppBootServiceProvider to app/Providers.

  2. Add $this->app->register(AppBootServiceProvider::class); to your AppServiceProvider->register() method

  3. Edit your docker-compose or .env to make sure the supervisor command limits workers to 1 and changes the log-level to stop sending http access logs.

SUPERVISOR_PHP_COMMAND: "/usr/bin/php -d variables_order=EGPCS /app/artisan octane:start --workers=1 --max-requests=300 --watch --poll --server=frankenphp --admin-port=2019 --port=443 --host=localhost --http-redirect --https --log-level=warn"

Changelog

2025-02-13: Added \Debugbar::disable(); to ServiceProvider to prevent debugbar logging this URI and filling logs 2025-02-11: Release

<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Route;
use Filament\Support\Facades\FilamentView;
use Filament\View\PanelsRenderHook;
use Symfony\Component\HttpFoundation\StreamedResponse;
class AppBootServiceProvider extends ServiceProvider
{
public function boot(): void
{
config(['app.boot' => time()]);
Route::get('/app-boot', function () {
\Debugbar::disable(); // If using barryvdh/laravel-debugbar prevent this URI being analysed
return response()->json(['timestamp' => config('app.boot')]);
});
FilamentView::registerRenderHook(
PanelsRenderHook::SCRIPTS_BEFORE,
fn (): string => <<<HTML
<script>
let localTimestamp = '';
function checkForUpdates() {
fetch('/app-boot')
.then(response => response.json())
.then(data => {
if (localTimestamp === '') {
localTimestamp = data.timestamp;
console.log('Initial timestamp:', localTimestamp);
} else if (localTimestamp !== data.timestamp) {
console.log('Timestamp changed. Refreshing...');
location.reload();
}
})
.catch(error => {
console.error('Error:', error);
setTimeout(() => {
location.reload();
}, 3000);
});
}
// Run immediately and then every 3 seconds
checkForUpdates();
setInterval(checkForUpdates, 3000);
</script>
HTML
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment