Updating Document Title in React Without Helmet

react-helmet has long been the default for managing the head in React, but it is an extra dependency with its own quirks — and modern React often does not need it. For client-rendered routes you can manage the title with a small hook over the native document API, and on React 19 you can render <title> and <meta> directly in JSX and let React hoist them. This keeps titles correct per route as part of programmatic title and meta tag updates.

Step-by-step fix

  1. Write a minimal title hook. Set document.title in an effect keyed to the route’s title.

    // useDocumentTitle.js
    import { useEffect } from 'react';
    export function useDocumentTitle(title) {
      useEffect(() => {
        const prev = document.title;
        document.title = title;
        return () => { document.title = prev; }; // restore on unmount
      }, [title]);
    }
  2. Manage meta tags by mutation, not appending. Reuse one tag per name so navigation does not pile up duplicates.

    // ✅ Update an existing meta tag in place
    function setMeta(name, content) {
      let tag = document.querySelector(`meta[name="${name}"]`);
      if (!tag) { tag = document.createElement('meta'); tag.name = name; document.head.appendChild(tag); }
      tag.content = content;
    }
  3. Prefer native metadata hoisting on React 19. Rendering metadata in JSX removes the need for any library and works during SSR.

    // ✅ React 19 hoists these into <head> automatically
    function ProductHead({ product }) {
      return (
        <>
          <title>{product.name}</title>
          <meta name="description" content={product.summary} />
        </>
      );
    }
  4. Set the title as early as possible for client-rendered routes so the render snapshot captures the correct value.

Validation

  • document.title matches the route on every navigation.
  • No duplicate meta tags accumulate as you navigate.
  • GSC URL Inspection rendered HTML shows the right title and description.
  • React 19 path: <title> appears in <head> in the SSR output, not buried in the body.

Reference

// Dependency-free head management for a route component
import { useDocumentTitle } from './useDocumentTitle';

export function ProductPage({ product }) {
  useDocumentTitle(`${product.name} — Example`); // SEO: per-route title
  useEffect(() => {
    setMeta('description', product.summary);       // mutate, never append
  }, [product]);
  return <ProductView product={product} />;
}

Frequently Asked Questions

Do I still need react-helmet in modern React? Not necessarily. React 19 hoists title and meta elements rendered anywhere in the tree into the document head, covering most needs natively. For earlier versions, a small custom hook that sets document.title and mutates meta tags is enough for many apps without the extra dependency.

Will a client-only title update be seen by Google? Google can pick up a client-set title after it renders the page, but it is more reliable when the title is in the server or prerendered HTML. For client-rendered routes, set the title as early as possible and ensure it is correct in the render snapshot.

← Back to Programmatic Title & Meta Tag Updates