Meta Tags & SEO
Phyre provides a powerful meta function for managing SEO tags on a per-route basis.
Basic Meta Tags
Export a meta function from any route to define meta tags:
jsx
// src/client/routes/about.jsx export default function About() { return <h1>About Us</h1>; } export function meta() { return [ { title: 'About Us - My App' }, { name: 'description', content: 'Learn more about our company' } ]; }
Meta Function Signature
The meta function receives useful context:
jsx
export function meta({ data, location, params, matches, error }) { return [ { title: `Post ${params.id} - My Blog` }, { name: 'description', content: data?.description } ]; }
| Parameter | Description |
|---|---|
data | Data returned from the route's loader |
location | Current location object |
params | Route parameters (e.g., { id: "123" }) |
matches | All matched routes (for nested routes) |
error | Error object if loader failed |
Using Loader Data
Combine meta with loaders for dynamic SEO:
jsx
// src/client/routes/posts/[id].jsx import { useLoaderData } from 'react-router'; export async function loader({ params }) { const res = await fetch(`http://localhost:3000/api/posts/${params.id}`); return res.json(); } export function meta({ data }) { return [ { title: `${data.title} - My Blog` }, { name: 'description', content: data.excerpt }, { property: 'og:title', content: data.title }, { property: 'og:description', content: data.excerpt }, { property: 'og:image', content: data.coverImage } ]; } export default function Post() { const post = useLoaderData(); return <article>{post.title}</article>; }
Supported Meta Tags
Title
jsx
export function meta() { return [ { title: 'My Page Title' } ]; }
Standard Meta Tags
jsx
export function meta() { return [ { name: 'description', content: 'Page description' }, { name: 'keywords', content: 'react, ssr, framework' }, { name: 'author', content: 'Your Name' } ]; }
Open Graph (Facebook)
jsx
export function meta() { return [ { property: 'og:title', content: 'Page Title' }, { property: 'og:description', content: 'Page description' }, { property: 'og:image', content: 'https://example.com/image.jpg' }, { property: 'og:url', content: 'https://example.com/page' }, { property: 'og:type', content: 'website' } ]; }
Twitter Cards
jsx
export function meta() { return [ { name: 'twitter:card', content: 'summary_large_image' }, { name: 'twitter:title', content: 'Page Title' }, { name: 'twitter:description', content: 'Page description' }, { name: 'twitter:image', content: 'https://example.com/image.jpg' } ]; }
Link Tags
jsx
export function meta() { return [ { tagName: 'link', rel: 'canonical', href: 'https://example.com/page' }, { tagName: 'link', rel: 'alternate', hreflang: 'es', href: 'https://example.com/es/page' } ]; }
Meta Tag Merging
When using nested layouts, meta tags from child routes override parent meta tags. The last route in the hierarchy wins.
jsx
// src/client/routes/_layout.jsx export function meta() { return [ { title: 'My App' }, { name: 'description', content: 'Default description' } ]; } // src/client/routes/about.jsx export function meta() { return [ { title: 'About - My App' } // Description inherited from layout ]; }
Result on /about:
• Title: "About - My App" (from about.jsx)
• Description: "Default description" (from _layout.jsx)
Dynamic Meta Based on Route Params
jsx
// src/client/routes/users/[id].jsx export async function loader({ params }) { const res = await fetch(`http://localhost:3000/api/users/${params.id}`); return res.json(); } export function meta({ data, params }) { if (!data) { return [ { title: 'User Not Found' } ]; } return [ { title: `${data.name} - User Profile` }, { name: 'description', content: `View ${data.name}'s profile` }, { property: 'og:title', content: data.name }, { property: 'og:image', content: data.avatar } ]; } export default function UserProfile() { const user = useLoaderData(); return <div>{user.name}</div>; }
Conditional Meta Tags
jsx
export function meta({ data }) { const tags = [ { title: data.title } ]; // Add image only if exists if (data.image) { tags.push({ property: 'og:image', content: data.image }); } // Add canonical if published if (data.published) { tags.push({ tagName: 'link', rel: 'canonical', href: `https://example.com/posts/${data.slug}` }); } return tags; }
Best Practices
- Always set a title - Every page should have a unique title
- Use descriptive descriptions - 150-160 characters is optimal
- Include Open Graph tags - For better social media sharing
- Use canonical URLs - Prevent duplicate content issues
- Keep meta functions pure - They may be called multiple times
