//
Search across all documentation pages
function BlogPost({ post }: { post: { title: string; description: string; author: string } }) {
return (
<article>
{/* These are hoisted to <head> automatically */}
<title>{post.title}</title>
<meta name="description" content={post.description} />
<meta name="author" content={post.author} />
<link rel="canonical" href={`https://example.com/blog/${post.title}`} />
<h1>{post.title}</h1>
<p>{post.description}</p>
</article>
);
}When to reach for this: Use built-in metadata whenever a component needs to set the page title, description, or other head tags. This eliminates the need for react-helmet or manual document.title effects.
// A multi-page app where each route sets its own metadata
function ProductPage({ product }: {
product: {
name: string;
description: string;
price: number;
image: string;
category:
What this demonstrates:
<title> rendered inline, automatically hoisted to <head><link rel="preload"> for performance optimization<link rel="stylesheet"> with precedence for CSS ordering<title>, <meta>, and <link> elements when rendered inside a component and hoists them to the document <head>.<head>. During client-side navigation, React updates the <head> by adding, removing, or updating the hoisted elements.<title> tags are deduplicated -- only the last rendered <title> wins if multiple components render one.<meta> tags are deduplicated by the name or property attribute. Two <meta name="description"> tags from different components will result in only the most recent one.<link rel="stylesheet"> tags support a precedence attribute that controls insertion order. Stylesheets with the same precedence are grouped together. React also deduplicates stylesheet links by href.<link> types (preload, icon, canonical, etc.) are hoisted but not deduplicated by default.Dynamic title with live data:
function ChatRoom({ roomName, unreadCount }: { roomName: string; unreadCount: number }) {
const titlePrefix = unreadCount > 0 ? `(${unreadCount}) ` : "";
return (
Nested components adding metadata:
function Layout({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
{/* Base metadata */}
<meta charSet="utf-8" />
Stylesheet precedence ordering:
function App() {
return (
<>
{/* "default" precedence loads first */}
<link rel="stylesheet" href="/base.css" precedence="default" />
{/* "high" precedence loads after "default" */}
<link rel="stylesheet"
<title>, <meta>, and <link> JSX elements use their standard HTML attribute types from React.JSX.IntrinsicElements.precedence attribute on <link> is a React-specific extension typed as string | undefined.<title> tags -- Only the last rendered <title> applies. If two sibling components both render <title>, the result depends on render order. Fix: Set <title> in only one component per route, typically the page-level component.metadata export and generateMetadata. Using both that and inline <title> tags can cause duplicates. Fix: Choose one approach per project. For Next.js apps, prefer the framework's metadata API.<head> in SSR HTML -- React hoists tags during rendering, but if you inspect the raw HTML response, they appear in <head>. If hydration mismatches occur, check for server/client differences. Fix: Ensure metadata values are deterministic (no Date.now() or Math.random()).<link rel="stylesheet"> without precedence -- If you omit precedence, the stylesheet link is treated as a regular link and is not deduplicated or ordered by React. Fix: Always add precedence to stylesheet links for React-managed ordering.<script> hoisting -- React 19 does not hoist <script> tags to <head>. Fix: Use preinit from react-dom for scripts, or place script tags in your HTML template.| Approach | When to choose |
|---|---|
| Built-in metadata (React 19) | Default choice for new React 19 projects |
Next.js metadata export | Next.js App Router projects with static or dynamic metadata |
react-helmet / react-helmet-async | React 18 or older projects |
Manual document.title in useEffect | Quick hack for client-only title changes |
HTML <head> in template | Static metadata that never changes |
<head> automatically<head> by adding, removing, or updating hoisted elements<title> applies -- React deduplicates them<title>, the result depends on render order<title> in only one component per route, typically the page-level component<meta> tags are deduplicated by the name or property attribute<meta name="description"> tags from different components result in only the most recent one<link> types (preload, icon, canonical) are hoisted but not deduplicated by defaultprecedence controls insertion order of stylesheets in the <head>precedence value are grouped togetherhref and suspends rendering until the stylesheet loadsfunction Page({ title, description, image }) {
return (
<>
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
function ChatRoom({ roomName, unreadCount }) {
const prefix = unreadCount > 0 ? `(${unreadCount}) ` : "";
return (
<>
<title>{prefix}{roomName} | Chat</title>
<meta charSet>, <link rel="icon">) and child pages add page-specific tags<title> overrides the layout's if both render one<meta> deduplication by name/property ensures no duplicatesmetadata export and generateMetadata which may conflict with inline tags<head>metadata API<script> tags to <head>preinit from react-dom for scripts, or place script tags in your HTML template<title>, <meta>, and <link> elementsprecedence, the stylesheet is treated as a regular link and is not deduplicated or ordered by Reactprecedence to stylesheet links you want React to manageprecedence attribute on <link> is a React-specific extension typed as string | undefined<title>, <meta>, and <link> use their standard HTML attribute types from React.JSX.IntrinsicElementsDate.now(), Math.random(), or other non-deterministic valuespreload and preinitThe title updates reactively as unreadCount changes.