11import { useEffect } from 'react' ;
22import { useLocation } from 'react-router-dom' ;
33
4- function useSeo ( {
5- title,
6- description,
7- keywords,
8- ogTitle,
9- ogDescription,
10- ogImage,
11- twitterCard,
12- twitterTitle,
13- twitterDescription,
14- twitterImage,
15- } ) {
4+ const BASE_URL = 'https://fezcode.com' ;
5+
6+ /**
7+ * useSeo - A simplified hook for managing site metadata.
8+ * Standardizes Title, Description, and Image across all platforms (OG, Twitter, etc.)
9+ */
10+ function useSeo ( { title, description, image, keywords, ogImage, twitterImage } ) {
1611 const location = useLocation ( ) ;
1712
1813 useEffect ( ( ) => {
19- // Helper to ensure absolute URLs
20- const toAbsoluteUrl = ( path ) => {
21- if ( ! path ) return path ;
22- if ( path . startsWith ( 'http' ) ) return path ;
23- return window . location . origin + ( path . startsWith ( '/' ) ? '' : '/' ) + path ;
24- } ;
25-
26- // Only update if we have a real title (prevents snapping loading state defaults too early)
14+ // 1. Only update if we have a valid title
2715 if ( ! title || title === 'Fezcodex' || title === 'fezcodex' ) return ;
2816
29- // Set document title
30- document . title = title ;
31-
32- // Set meta description
33- if ( description ) {
34- let metaDescription = document . querySelector ( 'meta[name="description"]' ) ;
35- if ( ! metaDescription ) {
36- metaDescription = document . createElement ( 'meta' ) ;
37- metaDescription . setAttribute ( 'name' , 'description' ) ;
38- document . head . appendChild ( metaDescription ) ;
39- }
40- metaDescription . setAttribute ( 'content' , description ) ;
41- }
42-
43- // Set meta keywords
44- if ( keywords ) {
45- let metaKeywords = document . querySelector ( 'meta[name="keywords"]' ) ;
46- if ( ! metaKeywords ) {
47- metaKeywords = document . createElement ( 'meta' ) ;
48- metaKeywords . setAttribute ( 'name' , 'keywords' ) ;
49- document . head . appendChild ( metaKeywords ) ;
50- }
51- metaKeywords . setAttribute ( 'content' , Array . isArray ( keywords ) ? keywords . join ( ', ' ) : keywords ) ;
52- }
53-
54- // Determine default images for apps
55- const isAppPath = location . pathname . startsWith ( '/apps' ) ;
56- const defaultAppImage = '/images/asset/ogtitle-apps.png' ;
57- const ogImagePath = ogImage || ( isAppPath ? defaultAppImage : null ) ;
58- const twitterImagePath = twitterImage || ( isAppPath ? defaultAppImage : null ) ;
59-
60- const finalOgImage = toAbsoluteUrl ( ogImagePath ) ;
61- const finalTwitterImage = toAbsoluteUrl ( twitterImagePath ) ;
17+ // 2. Prepare standardized values
18+ const currentUrl = BASE_URL + location . pathname ;
6219
63- // Set Open Graph meta tags
64- const ogTags = {
65- 'og:title' : ogTitle || title ,
66- 'og:description' : ogDescription || description ,
67- 'og:image' : finalOgImage ,
68- 'og:url' : window . location . origin + location . pathname ,
69- 'og:type' : location . pathname . startsWith ( '/blog' ) ? 'article' : 'website' ,
70- 'og:site_name' : 'Fezcodex' ,
71- } ;
20+ // Pick the first available image, fallback to default
21+ const rawImage = image || ogImage || twitterImage || '/images/asset/ogtitle.png' ;
22+ const finalImage = rawImage . startsWith ( 'http' )
23+ ? rawImage
24+ : BASE_URL + ( rawImage . startsWith ( '/' ) ? '' : '/' ) + rawImage ;
7225
73- Object . entries ( ogTags ) . forEach ( ( [ prop , content ] ) => {
26+ // 3. Helper to update/create meta tags
27+ const setMeta = ( attrName , attrValue , content ) => {
7428 if ( ! content ) return ;
75- let el = document . querySelector ( `meta[property ="${ prop } "]` ) ;
29+ let el = document . querySelector ( `meta[${ attrName } ="${ attrValue } "]` ) ;
7630 if ( ! el ) {
7731 el = document . createElement ( 'meta' ) ;
78- el . setAttribute ( 'property' , prop ) ;
32+ el . setAttribute ( attrName , attrValue ) ;
7933 document . head . appendChild ( el ) ;
8034 }
8135 el . setAttribute ( 'content' , content ) ;
82- } ) ;
83-
84- if ( finalOgImage ?. startsWith ( 'https' ) ) {
85- let el = document . querySelector ( 'meta[property="og:image:secure_url"]' ) ;
86- if ( ! el ) {
87- el = document . createElement ( 'meta' ) ;
88- el . setAttribute ( 'property' , 'og:image:secure_url' ) ;
89- document . head . appendChild ( el ) ;
90- }
91- el . setAttribute ( 'content' , finalOgImage ) ;
92- }
93-
94- // Set Twitter card meta tags
95- const twitterTags = {
96- 'twitter:card' : twitterCard || 'summary_large_image' ,
97- 'twitter:title' : twitterTitle || ogTitle || title ,
98- 'twitter:description' : twitterDescription || ogDescription || description ,
99- 'twitter:image' : finalTwitterImage || finalOgImage ,
100- 'twitter:url' : window . location . origin + location . pathname ,
10136 } ;
10237
103- Object . entries ( twitterTags ) . forEach ( ( [ name , content ] ) => {
104- if ( ! content ) return ;
105- let el = document . querySelector ( `meta[name="${ name } "]` ) ;
106- if ( ! el ) {
107- el = document . createElement ( 'meta' ) ;
108- el . setAttribute ( 'name' , name ) ;
109- document . head . appendChild ( el ) ;
110- }
111- el . setAttribute ( 'content' , content ) ;
112- } ) ;
38+ // 4. Update Document Title
39+ document . title = title ;
11340
114- // Canonical link
115- let canonicalLink = document . querySelector ( 'link[rel="canonical"]' ) ;
116- if ( ! canonicalLink ) {
117- canonicalLink = document . createElement ( 'link' ) ;
118- canonicalLink . setAttribute ( 'rel' , 'canonical' ) ;
119- document . head . appendChild ( canonicalLink ) ;
41+ // 5. Update Standard Meta Tags
42+ setMeta ( 'name' , 'description' , description ) ;
43+ if ( keywords ) {
44+ setMeta ( 'name' , 'keywords' , Array . isArray ( keywords ) ? keywords . join ( ', ' ) : keywords ) ;
12045 }
121- canonicalLink . setAttribute ( 'href' , window . location . origin + location . pathname ) ;
122-
123- return ( ) => {
124- // Cleanup: Restore defaults from index.html on unmount
125- const defaults = {
126- title : 'fezcodex' ,
127- description : 'codex by fezcode...' ,
128- ogTitle : 'Fezcodex - Personal Blog and Projects' ,
129- ogDescription : 'Discover logs, posts, projects, and stories from Fezcode.' ,
130- ogImage : toAbsoluteUrl ( '/images/asset/ogtitle.png' ) ,
131- ogUrl : 'https://fezcode.com/' ,
132- twitterCard : 'summary_large_image' ,
133- twitterTitle : 'Fezcodex - Personal Blog and Projects' ,
134- twitterDescription : 'Discover logs, posts, projects, and stories from Fezcode.' ,
135- twitterImage : toAbsoluteUrl ( '/images/asset/ogtitle.png' ) ,
136- twitterUrl : 'https://fezcode.com/' ,
137- } ;
138-
139- document . title = defaults . title ;
140-
141- const metaDescription = document . querySelector ( 'meta[name="description"]' ) ;
142- if ( metaDescription ) metaDescription . setAttribute ( 'content' , defaults . description ) ;
143-
144- const metaOgTitle = document . querySelector ( 'meta[property="og:title"]' ) ;
145- if ( metaOgTitle ) metaOgTitle . setAttribute ( 'content' , defaults . ogTitle ) ;
14646
147- const metaOgDescription = document . querySelector ( 'meta[property="og:description"]' ) ;
148- if ( metaOgDescription ) metaOgDescription . setAttribute ( 'content' , defaults . ogDescription ) ;
149-
150- const metaOgImage = document . querySelector ( 'meta[property="og:image"]' ) ;
151- if ( metaOgImage ) metaOgImage . setAttribute ( 'content' , defaults . ogImage ) ;
152-
153- const metaOgUrl = document . querySelector ( 'meta[property="og:url"]' ) ;
154- if ( metaOgUrl ) metaOgUrl . setAttribute ( 'content' , defaults . ogUrl ) ;
155-
156- const metaTwitterCard = document . querySelector ( 'meta[name="twitter:card"]' ) ;
157- if ( metaTwitterCard ) metaTwitterCard . setAttribute ( 'content' , defaults . twitterCard ) ;
158-
159- const metaTwitterTitle = document . querySelector ( 'meta[name="twitter:title"]' ) ;
160- if ( metaTwitterTitle ) metaTwitterTitle . setAttribute ( 'content' , defaults . twitterTitle ) ;
161-
162- const metaTwitterDescription = document . querySelector ( 'meta[name="twitter:description"]' ) ;
163- if ( metaTwitterDescription ) metaTwitterDescription . setAttribute ( 'content' , defaults . twitterDescription ) ;
164-
165- const metaTwitterImage = document . querySelector ( 'meta[name="twitter:image"]' ) ;
166- if ( metaTwitterImage ) metaTwitterImage . setAttribute ( 'content' , defaults . twitterImage ) ;
47+ // 6. Update Open Graph Tags (Standardized)
48+ setMeta ( 'property' , 'og:title' , title ) ;
49+ setMeta ( 'property' , 'og:description' , description ) ;
50+ setMeta ( 'property' , 'og:image' , finalImage ) ;
51+ if ( finalImage . toLowerCase ( ) . endsWith ( '.webp' ) ) {
52+ setMeta ( 'property' , 'og:image:type' , 'image/webp' ) ;
53+ } else if ( finalImage . toLowerCase ( ) . endsWith ( '.png' ) ) {
54+ setMeta ( 'property' , 'og:image:type' , 'image/png' ) ;
55+ } else if ( finalImage . toLowerCase ( ) . endsWith ( '.jpg' ) || finalImage . toLowerCase ( ) . endsWith ( '.jpeg' ) ) {
56+ setMeta ( 'property' , 'og:image:type' , 'image/jpeg' ) ;
57+ }
16758
168- const metaTwitterUrl = document . querySelector ( 'meta[name="twitter:url"]' ) ;
169- if ( metaTwitterUrl ) metaTwitterUrl . setAttribute ( 'content' , defaults . twitterUrl ) ;
59+ setMeta ( 'property' , 'og:url' , currentUrl ) ;
60+ setMeta ( 'property' , 'og:type' , location . pathname . startsWith ( '/blog' ) ? 'article' : 'website' ) ;
61+ setMeta ( 'property' , 'og:site_name' , 'Fezcodex' ) ;
62+ if ( finalImage . startsWith ( 'https' ) ) {
63+ setMeta ( 'property' , 'og:image:secure_url' , finalImage ) ;
64+ }
17065
171- const canonicalLink = document . querySelector ( 'link[rel="canonical"]' ) ;
172- if ( canonicalLink ) canonicalLink . setAttribute ( 'href ', defaults . ogUrl ) ;
173- } ;
174- } , [
175- location . pathname ,
176- title ,
177- description ,
178- keywords ,
179- ogTitle ,
180- ogDescription ,
181- ogImage ,
182- twitterCard ,
183- twitterTitle ,
184- twitterDescription ,
185- twitterImage ,
186- ] ) ;
66+ // 7. Update Twitter Tags (Standardized)
67+ setMeta ( 'name' , 'twitter:card ', 'summary_large_image' ) ;
68+ setMeta ( 'name' , 'twitter:title' , title ) ;
69+ setMeta ( 'name' , 'twitter:description' , description ) ;
70+ setMeta ( 'name' , 'twitter:image' , finalImage ) ;
71+ setMeta ( 'name' , 'twitter:url' , currentUrl ) ;
72+
73+ // 8. Update Canonical Link
74+ let canonical = document . querySelector ( 'link[rel="canonical"]' ) ;
75+ if ( ! canonical ) {
76+ canonical = document . createElement ( 'link' ) ;
77+ canonical . setAttribute ( 'rel' , 'canonical' ) ;
78+ document . head . appendChild ( canonical ) ;
79+ }
80+ canonical . setAttribute ( 'href' , currentUrl ) ;
81+ } , [ location . pathname , title , description , image , keywords , ogImage , twitterImage ] ) ;
18782}
18883
189- export default useSeo ;
84+ export default useSeo ;
0 commit comments