← Back to list

add-seo
by tech-with-seth
React Router 7 starter with Polar.sh, BetterAuth, Prisma, and Tailwind
⭐ 1🍴 0📅 Jan 24, 2026
SKILL.md
name: add-seo description: Add SEO meta tags, Open Graph tags, and structured data to routes. Use when optimizing pages for search engines or social sharing.
Add SEO
Adds SEO optimization including meta tags, Open Graph, Twitter cards, and structured data using React 19 patterns.
When to Use
- Adding page titles and descriptions
- Setting up Open Graph for social sharing
- Adding Twitter card metadata
- Implementing structured data (JSON-LD)
- User asks to "add SEO", "add meta tags", or "optimize for search"
React 19 Meta Pattern
Use native JSX tags, NOT meta() export.
import type { Route } from './+types/product';
export default function ProductPage({ loaderData }: Route.ComponentProps) {
const { product } = loaderData;
return (
<>
{/* Required */}
<title>{product.name} | Iridium</title>
<meta name="description" content={product.description} />
{/* Open Graph */}
<meta property="og:title" content={product.name} />
<meta property="og:description" content={product.description} />
<meta property="og:image" content={product.imageUrl} />
<meta property="og:type" content="product" />
<meta property="og:url" content={`https://example.com/products/${product.id}`} />
{/* Twitter Card */}
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={product.name} />
<meta name="twitter:description" content={product.description} />
<meta name="twitter:image" content={product.imageUrl} />
{/* Page content */}
<Container>
<h1>{product.name}</h1>
</Container>
</>
);
}
Basic Page SEO
export default function AboutPage() {
return (
<>
<title>About Us | Iridium</title>
<meta name="description" content="Learn about our mission, team, and values." />
<meta name="robots" content="index, follow" />
<Container>
{/* Page content */}
</Container>
</>
);
}
Dynamic SEO from Loader Data
import type { Route } from './+types/blog-post';
export async function loader({ params }: Route.LoaderArgs) {
const post = await getPost(params.slug);
return { post };
}
export default function BlogPost({ loaderData }: Route.ComponentProps) {
const { post } = loaderData;
const canonicalUrl = `https://example.com/blog/${post.slug}`;
return (
<>
<title>{post.title} | Blog | Iridium</title>
<meta name="description" content={post.excerpt} />
<link rel="canonical" href={canonicalUrl} />
{/* Article-specific Open Graph */}
<meta property="og:type" content="article" />
<meta property="og:title" content={post.title} />
<meta property="og:description" content={post.excerpt} />
<meta property="og:image" content={post.featuredImage} />
<meta property="og:url" content={canonicalUrl} />
<meta property="article:published_time" content={post.publishedAt} />
<meta property="article:author" content={post.author.name} />
<Container>
<article>
<h1>{post.title}</h1>
{/* Post content */}
</article>
</Container>
</>
);
}
Structured Data (JSON-LD)
import type { Route } from './+types/product';
export default function ProductPage({ loaderData }: Route.ComponentProps) {
const { product } = loaderData;
const structuredData = {
'@context': 'https://schema.org',
'@type': 'Product',
name: product.name,
description: product.description,
image: product.imageUrl,
offers: {
'@type': 'Offer',
price: product.price,
priceCurrency: 'USD',
availability: 'https://schema.org/InStock',
},
};
return (
<>
<title>{product.name} | Iridium</title>
<meta name="description" content={product.description} />
{/* Structured Data */}
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(structuredData),
}}
/>
<Container>
{/* Product content */}
</Container>
</>
);
}
Organization Structured Data
const organizationData = {
'@context': 'https://schema.org',
'@type': 'Organization',
name: 'Iridium',
url: 'https://example.com',
logo: 'https://example.com/logo.png',
sameAs: [
'https://twitter.com/iridium',
'https://github.com/iridium',
],
};
Breadcrumb Structured Data
const breadcrumbData = {
'@context': 'https://schema.org',
'@type': 'BreadcrumbList',
itemListElement: [
{
'@type': 'ListItem',
position: 1,
name: 'Home',
item: 'https://example.com',
},
{
'@type': 'ListItem',
position: 2,
name: 'Products',
item: 'https://example.com/products',
},
{
'@type': 'ListItem',
position: 3,
name: product.name,
item: `https://example.com/products/${product.id}`,
},
],
};
No-Index Pages
For pages that shouldn't appear in search:
export default function PrivatePage() {
return (
<>
<title>Private Page | Iridium</title>
<meta name="robots" content="noindex, nofollow" />
{/* Content */}
</>
);
}
SEO Checklist
- Unique
<title>for each page (50-60 chars) -
<meta name="description">(150-160 chars) - Open Graph tags for social sharing
- Twitter Card tags
- Canonical URL for duplicate content
- Structured data where appropriate
- No-index on private pages
Anti-Patterns
- Using
meta()export (old pattern) - Duplicate titles across pages
- Missing descriptions
- Descriptions over 160 characters
- Missing Open Graph image
- Using same meta on all pages
Full Reference
See .github/instructions/seo.instructions.md for comprehensive documentation.
Score
Total Score
65/100
Based on repository quality metrics
✓SKILL.md
SKILL.mdファイルが含まれている
+20
✓LICENSE
ライセンスが設定されている
+10
○説明文
100文字以上の説明がある
0/10
○人気
GitHub Stars 100以上
0/15
✓最近の活動
1ヶ月以内に更新
+10
○フォーク
10回以上フォークされている
0/5
✓Issue管理
オープンIssueが50未満
+5
✓言語
プログラミング言語が設定されている
+5
✓タグ
1つ以上のタグが設定されている
+5
Reviews
💬
Reviews coming soon

