How to set html tag title from a route #1056
Replies: 13 comments 12 replies
-
Another option that I am considering is to extract the |
Beta Was this translation helpful? Give feedback.
-
Following this diccussion, I've seen https://tanstack.com/router/v1/docs/guide/router-context#processing-accumulated-route-context but not sure how to adjust Vite config to be able to have the |
Beta Was this translation helpful? Give feedback.
-
I’m working on a first class solution for title tags.
…On Jan 23, 2024 at 6:36 PM -0700, Dominic Garms ***@***.***>, wrote:
Following this diccussion, I've seen https://tanstack.com/router/v1/docs/guide/router-context#processing-accumulated-route-context but not sure how to adjust Vite config to be able to have the <head/> section inside of __root.tsx
—
Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you are subscribed to this thread.Message ID: ***@***.***>
|
Beta Was this translation helpful? Give feedback.
-
I'd be keen for a first-class solution too. I had to adapt this solution from my previous React Router version... For the moment I have a The It's not perfect and there's actually no type-safety between my route data and the meta data argument, but it does the job pretty well - I just have to be very mindful of the loader data and manually provide the types to |
Beta Was this translation helpful? Give feedback.
-
@tannerlinsley any update when a first class solution might land, we're evaluating swapping to TanStack Router and this is one of the core pieces we're considering, especially when combined with Streaming responses. I noted the code/examples cover the Meta data, (except I couldn't find an example which loads in data for the tags), and wondered if this was the final API, or if it was a matter of just adding docs? |
Beta Was this translation helpful? Give feedback.
-
@tannerlinsley thanks for the wonderful library, looking forward to use the title tag feature in TanStack Router |
Beta Was this translation helpful? Give feedback.
-
For now, here's an example using accumulated route context: interface RootContext {
getTitle?: () => string | Promise<string>;
}
function Root() {
const matches = useMatches();
useEffect(() => {
// Set document title based on lowest matching route with a title
const breadcrumbPromises = [...matches]
.reverse()
.map((match, index) => {
if (!('getTitle' in match.routeContext)) {
if (index === 0 && import.meta.env.DEV) {
// eslint-disable-next-line no-console
console.warn('no getTitle', match.pathname, match);
}
return undefined;
}
const { routeContext } = match;
return routeContext.getTitle();
})
.filter(Boolean);
void Promise.all(breadcrumbPromises).then((titles) => {
document.title = titles.join(' · ');
return titles;
});
}, [matches]);
return <Outlet />;
}
export const Route = createRootRouteWithContext<RootContext>()({
beforeLoad: () => ({ getTitle: () => 'App Name' }),
component: Root,
}); // In another route file:
export const Route = createFileRoute('/test/')({
beforeLoad: () => ({ getTitle: () => 'Test' }),
component: () => <Test />,
}); This results in title like:
And supports further nesting. |
Beta Was this translation helpful? Give feedback.
-
I am now relying on react-helmet-async for head markup in my fastrat React + Fastify starter kit. |
Beta Was this translation helpful? Give feedback.
-
I've opted for a much simpler solution relying on the const TITLE = 'Your Company';
export const Route = createRootRouteWithContext<RootRouteContext>()({
meta: () => [
{
title: TITLE,
},
],
component: RootComponent,
});
function Meta({ children }: { children: ReactNode }) {
const matches = useMatches();
const meta = matches.at(-1)?.meta?.find((meta) => meta.title);
useEffect(() => {
document.title = [meta?.title, TITLE].filter(Boolean).join(' · ');
}, [meta]);
return children;
}
function RootComponent() {
return (
<Meta>
<Outlet />
</Meta>
);
} |
Beta Was this translation helpful? Give feedback.
-
To build on @Rendez solution, I wrote a simple hook that gathers up all the titles, and exposes them via prop. You can easily combine this with the import { useMatches } from "@tanstack/react-router";
import {
createContext,
PropsWithChildren,
useContext,
useEffect,
useState,
} from "react";
const BreadcrumbContext = createContext<
| {
breadcrumbs: string[];
getBreadcrumbString: (separator?: string) => string;
}
| undefined
>(undefined);
export const BreadcrumbProvider = ({ children }: PropsWithChildren) => {
const [breadcrumbs, setBreadcrumbs] = useState<string[]>([]);
const matches = useMatches();
useEffect(() => {
const meta = matches
.map((x) => x.meta?.find((meta) => meta.title)?.title)
.filter((x) => x !== undefined);
setBreadcrumbs(meta);
}, [matches]);
const getBreadcrumbString = (separator = " > ") => {
if (!breadcrumbs) return "";
return breadcrumbs?.join(separator);
};
return (
<BreadcrumbContext.Provider value={{ breadcrumbs, getBreadcrumbString }}>
{children}
</BreadcrumbContext.Provider>
);
};
export const useBreadcrumbs = () => {
const breadcrumbs = useContext(BreadcrumbContext);
if (!breadcrumbs)
throw new Error("useBreadcrumbs must be used within BreadcrumbProvider");
return breadcrumbs;
};
/// access breadcrumb data from any where via
/// const { breadcrumbs, getBreadcrumbString } = useBreadcrumbs(); Adjust your root module to be similar to the following: function RootComponent() {
return (
<BreadcrumbProvider>
<Meta>
<Outlet />
</Meta>
</BreadcrumbProvider>
);
} |
Beta Was this translation helpful? Give feedback.
-
hey @tannerlinsley , I’ve seen the
but I don't see my page title changing... is this API still experimental? |
Beta Was this translation helpful? Give feedback.
-
Has this been fixed ? I just tried setting a meta attribute inside createFileRoute and it seemd to work!
|
Beta Was this translation helpful? Give feedback.
-
This is what I currently use and works well. You can add more features to it. import { useEffect } from "react";
interface HelmetProps {
title?: string;
description?: string;
keywords?: string;
}
const Helmet: React.FC<HelmetProps> = ({ title, description, keywords }) => {
useEffect(() => {
// Set document title if provided
if (title) {
document.title = title;
}
// Set meta description if provided
if (description) {
let metaDescription = document.querySelector('meta[name="description"]');
if (!metaDescription) {
metaDescription = document.createElement("meta");
(metaDescription as HTMLMetaElement).name = "description";
document.head.appendChild(metaDescription);
}
(metaDescription as HTMLMetaElement).content = description;
}
// Set meta keywords if provided
if (keywords) {
let metaKeywords = document.querySelector('meta[name="keywords"]');
if (!metaKeywords) {
metaKeywords = document.createElement("meta");
(metaKeywords as HTMLMetaElement).name = "keywords";
document.head.appendChild(metaKeywords);
}
(metaKeywords as HTMLMetaElement).content = keywords;
}
}, [title, description, keywords]);
return null; // This component does not render any visible UI
};
export { Helmet }; |
Beta Was this translation helpful? Give feedback.
-
I can't see this from the examples, I am looking for a way to set HTML page title from a route definition.
This is challenging because the
<head />
element is defined in the root component__root.tsx
and other routes don't have access to it.At the moment I am relying on rendering an hidden HTML element that I copy to the page head, before sending the code to the browser.
Which works well, until I want to find a way to apply the streaming technique and then I am lost!
Beta Was this translation helpful? Give feedback.
All reactions