The old way
For years the answer to "how do I set the page title in a SPA?" was
react-helmet (or its async-safe sibling react-helmet-async).
You wrapped your app in a <HelmetProvider> and rendered a <Helmet>
element somewhere deep in your tree.
What React 19 changed
React 19 treats certain elements specially. If you render this anywhere in your component tree:
<title>{post.title} — My Site</title>
<meta name="description" content={post.excerpt} />
<link rel="canonical" href={canonical} />
…React hoists them straight into the document <head>, deduplicates by
attribute, and even handles the async and defer flags on <script>.
Migration
For my portfolio it was a straight find-and-replace:
- Remove
<HelmetProvider>frommain.tsx. - Replace every
<Helmet>...</Helmet>with the bare children. npm uninstall react-helmet-async.
That's it. Same SEO output, one less dependency, no provider, no context.
Caveats
- Server-rendering still needs a flush mechanism (React's official RSC story handles this).
- For prerendered static pages, my custom
scripts/prerender.tsinjects the meta tags into the HTML shell directly so crawlers don't need JS at all.
