Letzten Dienstag hat mich Mira, die bei unserem Inertia-Admin-Panel die Frontend-Hälfte verantwortet, mit einer Bildschirmaufnahme angepingt. Sie öffnet die Vorschau für den Rechnungsexport, die eine Weile braucht, weil sie rund elftausend Tabellenzeilen rendert. Dann tippt sie in der Sidebar-Suche einen Kundennamen ein, und die Suchergebnisse hängen einfach fest, wie eingefroren, bis die Exportvorschau fertig ist. „Warum interessiert sich die Suche für den Export?“, schrieb sie. Berechtigte Frage. Auf der PHP-Seite stellen wir sie uns schon lange nicht mehr, weil unsere Runtime sie für uns beantwortet hat.

Überleg mal, was ein PHP-FPM-Pool eigentlich macht. Ein Worker hängt an einer Report-Query, die 9 Sekunden dauert, und der nächste Request landet bei einem anderen Worker, erledigt seinen Job und ist nach 40 ms zurück, ohne je zu erfahren, dass es diesen Report überhaupt gibt. Shared-Nothing ist eine Gewohnheit, die uns gar nicht mehr auffällt. Jeder Request lebt und stirbt für sich allein, also kann ein langsamer einen unbeteiligten nicht langsam aussehen lassen. Soweit ich die Änderungen in React 19.3 verstehe, übernimmt die Client-Seite jetzt genau diese Gewohnheit für Transitions, und ich halte das für die richtige Entscheidung. Nebenbei wird das Frontend dadurch ein Ort, an dem PHP-Leute ganz natürlich denken können.

Erst mal die Fakten. Eine Transition ist alles, was du in startTransition() einpackst, und sie sagt React: Dieses Update ist echt, darf aber allem Dringenden den Vortritt lassen, etwa einem Tastendruck. Vor 19.3 konnte React zwei gleichzeitig laufende Transitions aneinanderkoppeln, sodass die billige am Ende auf die teure warten musste. In 19.3 können Transitions, die nichts miteinander zu tun haben, unabhängig voneinander rendern. Miras Suche und ihre Exportvorschau werden zu zwei FPM-Workern statt zu einem Worker mit einer langen Schlange dahinter. Der Artikel, der mich darauf gebracht hat, betont ausdrücklich, dass der Gewinn davon abhängt, was du renderst und ob sich die Updates wirklich gegenseitig berühren, und das würde ich doppelt unterstreichen.

Es gibt hier einen starken Einwand, und den will ich ernst nehmen. Nichts davon macht irgendetwas schneller. Eine Komponente, die 400 ms zum Rendern braucht, braucht weiterhin 400 ms. React ändert nur, wann diese Zeit verbraucht wird und was sich vordrängeln darf. Ein Skeptiker würde sagen, dass schlaueres Scheduling eine hervorragende Methode ist, eine aufgeblähte Komponente zu verstecken, so wie ein großer FPM-Pool dich eine furchtbare Query ignorieren lässt, bis die Datenbank am Monatsende umkippt. Ich habe dieses Monatsende erlebt. Der Pool hat die Seite am Laufen gehalten, und er hat auch zwei Jahre lang verhindert, dass irgendwer die Query repariert.

Ja, Isolation kann zur Ausrede werden. Trotzdem lande ich auf der Seite derer, die sie begrüßen, weil die Alternative schlimmer ist. Wenn alles gekoppelt ist, zahlt der Nutzer bei jeder Interaktion für deinen langsamsten Code, auch bei denen, die ihn nie berührt haben. Mit Isolation zahlt er nur für das, was er tatsächlich angefragt hat. Das ist der bessere Standard, und er hält dich nicht vom Profiling ab. Eher macht er Profiling ehrlicher: Sobald die Exportvorschau die Suche nicht mehr mit runterzieht, tauchen ihre 400 ms im Profiler als eigene Kosten auf, statt sich in den Zahlen aller anderen zu verstecken.

Was ich als Backend-Entwickler daraus mitnehme, hat weniger mit React-APIs zu tun als mit der Form unserer Responses. Miras Exportvorschau und ihre Suche rufen beide Laravel-Endpoints auf. Wenn sich diese zwei Endpoints einen Session-Lock teilen oder bei jedem Inertia-Visit ein riesiger Props-Payload neu gebaut wird, kann der Client so clever schedulen, wie er will: Auf dem Server bleiben die beiden Arbeitspakete trotzdem aneinandergekettet. Die Unabhängigkeit muss bis ganz nach unten durchgehen, damit der Scheduler überhaupt etwas damit anfangen kann. Wie man jedem Teil der UI einen eigenen schlanken Endpoint gibt, wissen wir längst: Partial Reloads, getrennte Routen, keine Session-Writes bei rein lesenden Calls. Jetzt gibt es dafür eine handfeste Belohnung.

Fairerweise muss man sagen, dass Transitions nie für alles gedacht waren. Ein Controlled Input muss nach wie vor sofort aktualisiert werden, und das übliche Muster gilt weiter: Den Wert, in den du tippst, behandelst du als dringend, und nur den teuren abgeleiteten State schiebst du in startTransition(). Daran hat sich nichts geändert. Neu ist, dass du jetzt mehrere solcher Hintergrund-Updates laufen lassen kannst, ohne dass sie als ein einziger, zäher Klumpen zu dir zurückkommen.

Wirklich unsicher bin ich bei der Teamdynamik. Bei uns ist die Person, die den Endpoint schreibt, selten dieselbe, die das Update in startTransition() einpackt, und Isolation auf der einen Seite zahlt sich nur aus, wenn die andere Seite mitspielt. Deshalb an alle, die PHP-Backends für React-Frontends ausliefern: Wenn die UI ruckelt, wer ist bei euch für den Fix zuständig, und hat eine Änderung wie 19.3 diese Grenze bei euch schon mal verschoben?