React 19 in production: what actually changed
I rebuilt this site on React 19 and Vite 8. Here is what the release genuinely improves, what it quietly removes, and the smooth-scroll bug that taught me the most about how the library handles events.
This site runs on React 19 with Vite 8 and TypeScript. Rebuilding it was a good excuse to find out which parts of the release matter in a real project rather than in a changelog, and the honest summary is that the headline features mattered less than the small removals.
The ref cleanup that fixes a whole bug class
Ref callbacks can now return a cleanup function, the same way an effect can. Before this, attaching something to a DOM node through a ref meant you had to detach it somewhere else, usually in an effect with a dependency array that had to be kept in agreement with the ref. The two lived apart and drifted apart.
Now setup and teardown sit in the same function, and it is no longer possible to write one without the other staring at you. For anything that attaches an observer, a listener, or a third-party instance to a node, this is the most useful change in the release.
forwardRef is gone as a requirement
Function components take ref as an ordinary prop now. That sounds cosmetic and it removes a real tax: every wrapper component in a design system that existed purely to thread a ref through can lose a layer. Fewer wrappers means shallower component trees and stack traces you can read.
Metadata belongs in the component now
Rendering a title or meta tag anywhere in the tree hoists it into the document head. For a single-page app this removes a small pile of imperative code that used to reach out and mutate the head on route change.
It does not remove the need for prerendered HTML, and that distinction cost me time. Crawlers and link unfurlers read the HTML that arrives from the server, and a tag rendered by JavaScript after hydration is invisible to a great deal of that tooling. My build still emits a static HTML file per route with the correct title, description, canonical link, share card and structured data baked in. React 19 makes the runtime side pleasant. It does not make the build step optional.
If a share preview has to run your JavaScript to be correct, assume it will be wrong.
The bug that taught me the most
I use a smooth-scroll library that intercepts wheel events at the document level and animates the scroll position itself. It feels good and it broke every overlay on the site in a way that took a while to understand.
The pattern is standard: open a modal, lock background scrolling, let the modal's own content scroll. What actually happened was that the smooth-scroll library was consuming the wheel event before the overlay ever received it. Scrolling inside the modal did nothing at all, because the event was being swallowed upstream and turned into an animation on a page that was supposed to be locked.
The lesson is not really about that library. It is that any library which intercepts input at the document level becomes part of the contract of every component beneath it, and nothing in your types will tell you so. Overlays, drawers, command palettes and nested scroll regions are where these two designs collide, and the collision surfaces as an element that simply refuses to scroll, with no error anywhere.
Performance was mostly about what I shipped, not how I rendered
This site carries a 3D globe and an animation library, and my first honest audit was unflattering. Almost all of the recovery came from ordinary decisions rather than anything React-specific:
- Lazy-load every route so the heavy 3D dependency is not in the initial bundle for someone reading a blog post.
- Size and format images properly, which is still the largest single win available on most sites.
- Let the build emit static HTML per route so the first paint does not wait on the framework to boot.
- Be honest about whether a decorative dependency earns its download cost.
React 19's own improvements are real but they are not what moves a score. Bundle size and image weight move a score.
The compiler question
The React compiler is the change people ask about most, and my position is that it removes a category of chore rather than a category of bug. Manual memoisation was always a performance tool that people applied defensively and inconsistently, often making things marginally worse. Having the compiler handle it means one less thing to argue about in review.
It does not fix an expensive render. If a component does too much work, it will still do too much work, just memoised. The measurable wins in my project all came from shipping less code, not from rendering it more cleverly.
Would I upgrade a real project
Yes, and I would expect it to be dull, which is the highest compliment available for a major version. The ref cleanup and dropping forwardRef genuinely simplify code. Document metadata in components is a nice tidy-up with a sharp edge if you assume it replaces prerendering. Everything else is the same work it always was: ship less JavaScript, compress your images, and find out which library is quietly eating your events.