diff --git a/packages/react-router/src/Asset.tsx b/packages/react-router/src/Asset.tsx
index 79ceb8ed77..258623bcf0 100644
--- a/packages/react-router/src/Asset.tsx
+++ b/packages/react-router/src/Asset.tsx
@@ -16,11 +16,7 @@ export function Asset({
}: RouterManagedTag & { nonce?: string }): React.ReactElement | null {
switch (tag) {
case 'title':
- return (
-
- {children}
-
- )
+ return {children}
case 'meta':
return
case 'link':
@@ -40,6 +36,48 @@ export function Asset({
}
}
+// Track if we've taken control of the title
+let titleControlled = false
+
+function Title({
+ attrs,
+ children,
+}: {
+ attrs?: Record
+ children?: string
+}) {
+ const router = useRouter()
+
+ React.useEffect(() => {
+ if (typeof children === 'string') {
+ // On the first title update, clean up any existing titles
+ if (!titleControlled) {
+ // Remove all existing title tags - router will now manage the title
+ const existingTitles = Array.from(document.querySelectorAll('title'))
+ existingTitles.forEach(titleEl => {
+ titleEl.remove()
+ })
+ titleControlled = true
+ }
+
+ // Set document.title directly - no DOM title tags needed on client
+ document.title = children
+ }
+ }, [children])
+
+ if (!router.isServer) {
+ // On client, don't render title tag - we manage document.title directly
+ return null
+ }
+
+ // On server, render title tag normally for SSR
+ return (
+
+ {children}
+
+ )
+}
+
function Script({
attrs,
children,