Skip to content

Commit 0029f2b

Browse files
committed
Add Open Graph and Twitter Card meta tags for social sharing
- Create useMetaTags hook to dynamically update meta tags for each page - Add Open Graph tags (og:title, og:description, og:image, og:url) to story and project detail pages - Add Twitter Card tags for better social media previews - Update index.html with default meta tags for homepage - Thumbnail images now display when links are shared on social platforms - Proper absolute URL handling for images in meta tags
1 parent f602e49 commit 0029f2b

5 files changed

Lines changed: 131 additions & 0 deletions

File tree

index.html

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,20 @@
44
<head>
55
<meta charset="UTF-8" />
66
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7+
<meta name="description" content="Computer scientist and Application Systems Analyst Senior bridging technology, education, and human-centered design" />
8+
9+
<!-- Open Graph / Facebook -->
10+
<meta property="og:type" content="website" />
11+
<meta property="og:title" content="Regan Maharjan Portfolio Website" />
12+
<meta property="og:description" content="Computer scientist and Application Systems Analyst Senior bridging technology, education, and human-centered design" />
13+
<meta property="og:image" content="/assets/raylogo.png" />
14+
15+
<!-- Twitter -->
16+
<meta name="twitter:card" content="summary_large_image" />
17+
<meta name="twitter:title" content="Regan Maharjan Portfolio Website" />
18+
<meta name="twitter:description" content="Computer scientist and Application Systems Analyst Senior bridging technology, education, and human-centered design" />
19+
<meta name="twitter:image" content="/assets/raylogo.png" />
20+
721
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
822
<link rel="alternate icon" href="/assets/raylogo.png" />
923
<link rel="preconnect" href="https://fonts.googleapis.com">

src/components/pages/ProjectDetail.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { Button } from '../ui/button';
66
import { ImageWithFallback } from '../figma/ImageWithFallback';
77
import { ExternalLink, Github, FileText, ArrowLeft, Share2, Copy, Check } from 'lucide-react';
88
import contentData from '../../data/content';
9+
import { useMetaTags } from '../../utils/useMetaTags';
910

1011
export function ProjectDetail() {
1112
const { projectId } = useParams<{ projectId: string }>();
@@ -28,6 +29,15 @@ export function ProjectDetail() {
2829
}
2930
}, [selectedProject, navigate]);
3031

32+
// Update meta tags for social sharing
33+
useMetaTags({
34+
title: selectedProject ? `${selectedProject.title} | Projects` : 'Projects',
35+
description: selectedProject?.description || 'Selected Projects',
36+
image: selectedProject?.imageUrl || (contentData.assets.images.projects[0] || '/assets/raylogo.png'),
37+
url: selectedProject ? window.location.href : undefined,
38+
type: 'article',
39+
});
40+
3141
const handleShare = async () => {
3242
const url = window.location.href;
3343

src/components/pages/StoryDetail.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { Heart, Globe, BookOpen, Laptop, Users, Zap, ArrowLeft, Calendar, Share2
55
import { Button } from '../ui/button';
66
import contentData from '../../data/content';
77
import { useState } from 'react';
8+
import { useMetaTags } from '../../utils/useMetaTags';
9+
import { getImageUrl } from '../../utils/imageUtils';
810

911
const iconMap: Record<string, typeof Globe> = {
1012
'Globe': Globe,
@@ -76,6 +78,15 @@ export function StoryDetail() {
7678
}
7779
}, [selectedStory, navigate]);
7880

81+
// Update meta tags for social sharing
82+
useMetaTags({
83+
title: selectedStory ? `${selectedStory.title} | Stories of Impact` : 'Stories of Impact',
84+
description: selectedStory?.excerpt || 'Stories of Impact',
85+
image: selectedStory ? getImageUrl(selectedStory.thumbnail) : getImageUrl(contentData.assets.images.impact.ruralSchool),
86+
url: selectedStory ? window.location.href : undefined,
87+
type: 'article',
88+
});
89+
7990
const handleShare = async () => {
8091
const url = window.location.href;
8192

src/components/pages/StoryOfAdventureDetail.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { Button } from '../ui/button';
66
import { InstagramFrame } from '../ui/InstagramFrame';
77
import contentData from '../../data/content';
88
import { getImageUrl } from '../../utils/imageUtils';
9+
import { useMetaTags } from '../../utils/useMetaTags';
910

1011
const iconMap: Record<string, typeof Globe> = {
1112
'Globe': Globe,
@@ -88,6 +89,15 @@ export function StoryOfAdventureDetail() {
8889
}
8990
}, [selectedStory, navigate]);
9091

92+
// Update meta tags for social sharing
93+
useMetaTags({
94+
title: selectedStory ? `${selectedStory.title} | Stories of Adventure` : 'Stories of Adventure',
95+
description: selectedStory?.excerpt || 'Stories of Adventure',
96+
image: selectedStory ? getImageUrl(selectedStory.thumbnail) : contentData.assets.images.impact.mountainVillage,
97+
url: selectedStory ? window.location.href : undefined,
98+
type: 'article',
99+
});
100+
91101
const handleShare = async () => {
92102
const url = window.location.href;
93103

src/utils/useMetaTags.ts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import { useEffect } from 'react';
2+
3+
interface MetaTagsProps {
4+
title: string;
5+
description: string;
6+
image: string;
7+
url?: string;
8+
type?: string;
9+
}
10+
11+
export function useMetaTags({ title, description, image, url, type = 'article' }: MetaTagsProps) {
12+
useEffect(() => {
13+
// Get base URL
14+
const baseUrl = window.location.origin;
15+
const currentUrl = url || window.location.href;
16+
17+
// Convert relative image URL to absolute
18+
let imageUrl = image;
19+
if (!image.startsWith('http')) {
20+
// Handle relative paths
21+
if (image.startsWith('/')) {
22+
imageUrl = `${baseUrl}${image}`;
23+
} else if (image.startsWith('./') || image.startsWith('../')) {
24+
// Handle relative paths from current location
25+
imageUrl = `${baseUrl}/${image}`;
26+
} else {
27+
// Assume it's in assets folder
28+
imageUrl = `${baseUrl}/assets/${image}`;
29+
}
30+
}
31+
32+
// Update or create meta tags
33+
const updateMetaTag = (property: string, content: string, isProperty = true) => {
34+
const selector = isProperty ? `meta[property="${property}"]` : `meta[name="${property}"]`;
35+
let element = document.querySelector(selector) as HTMLMetaElement;
36+
37+
if (!element) {
38+
element = document.createElement('meta');
39+
if (isProperty) {
40+
element.setAttribute('property', property);
41+
} else {
42+
element.setAttribute('name', property);
43+
}
44+
document.head.appendChild(element);
45+
}
46+
47+
element.setAttribute('content', content);
48+
};
49+
50+
// Update title
51+
document.title = title;
52+
53+
// Open Graph tags
54+
updateMetaTag('og:title', title);
55+
updateMetaTag('og:description', description);
56+
updateMetaTag('og:image', imageUrl);
57+
updateMetaTag('og:url', currentUrl);
58+
updateMetaTag('og:type', type);
59+
60+
// Twitter Card tags
61+
updateMetaTag('twitter:card', 'summary_large_image', false);
62+
updateMetaTag('twitter:title', title, false);
63+
updateMetaTag('twitter:description', description, false);
64+
updateMetaTag('twitter:image', imageUrl, false);
65+
66+
// Standard meta tags
67+
updateMetaTag('description', description, false);
68+
69+
// Cleanup function to restore default meta tags
70+
return () => {
71+
document.title = 'Regan Maharjan Portfolio Website';
72+
const defaultDescription = 'Computer scientist and Application Systems Analyst Senior bridging technology, education, and human-centered design';
73+
updateMetaTag('og:title', 'Regan Maharjan Portfolio Website');
74+
updateMetaTag('og:description', defaultDescription);
75+
updateMetaTag('og:image', `${baseUrl}/assets/raylogo.png`);
76+
updateMetaTag('og:url', baseUrl);
77+
updateMetaTag('og:type', 'website');
78+
updateMetaTag('twitter:card', 'summary_large_image', false);
79+
updateMetaTag('twitter:title', 'Regan Maharjan Portfolio Website', false);
80+
updateMetaTag('twitter:description', defaultDescription, false);
81+
updateMetaTag('twitter:image', `${baseUrl}/assets/raylogo.png`, false);
82+
updateMetaTag('description', defaultDescription, false);
83+
};
84+
}, [title, description, image, url, type]);
85+
}
86+

0 commit comments

Comments
 (0)