@@ -16,252 +16,163 @@ function useSeo({
1616 const location = useLocation ( ) ;
1717
1818 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)
27+ if ( ! title || title === 'Fezcodex' || title === 'fezcodex' ) return ;
28+
1929 // Set document title
20- if ( title ) {
21- document . title = title ;
22- }
30+ document . title = title ;
2331
2432 // Set meta description
2533 if ( description ) {
26- const metaDescription = document . querySelector (
27- 'meta[name="description"]' ,
28- ) ;
29- if ( metaDescription ) {
30- metaDescription . setAttribute ( 'content' , description ) ;
31- } else {
32- const newMeta = document . createElement ( 'meta' ) ;
33- newMeta . setAttribute ( 'name' , 'description' ) ;
34- newMeta . setAttribute ( 'content' , description ) ;
35- document . head . appendChild ( newMeta ) ;
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 ) ;
3639 }
40+ metaDescription . setAttribute ( 'content' , description ) ;
3741 }
3842
3943 // Set meta keywords
4044 if ( keywords ) {
41- const metaKeywords = document . querySelector ( 'meta[name="keywords"]' ) ;
42- if ( metaKeywords ) {
43- metaKeywords . setAttribute ( 'content' , keywords ) ;
44- } else {
45- const newMeta = document . createElement ( 'meta' ) ;
46- newMeta . setAttribute ( 'name' , 'keywords' ) ;
47- newMeta . setAttribute ( 'content' , keywords ) ;
48- document . head . appendChild ( newMeta ) ;
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 ) ;
4950 }
51+ metaKeywords . setAttribute ( 'content' , Array . isArray ( keywords ) ? keywords . join ( ', ' ) : keywords ) ;
5052 }
5153
5254 // Determine default images for apps
5355 const isAppPath = location . pathname . startsWith ( '/apps' ) ;
5456 const defaultAppImage = '/images/asset/ogtitle-apps.png' ;
55- const finalOgImage = ogImage || ( isAppPath ? defaultAppImage : null ) ;
56- const finalTwitterImage =
57- twitterImage || ( isAppPath ? defaultAppImage : null ) ;
57+ const ogImagePath = ogImage || ( isAppPath ? defaultAppImage : null ) ;
58+ const twitterImagePath = twitterImage || ( isAppPath ? defaultAppImage : null ) ;
5859
59- // Set Open Graph meta tags
60- if ( ogTitle ) {
61- const metaOgTitle = document . querySelector ( 'meta[property="og:title"]' ) ;
62- if ( metaOgTitle ) {
63- metaOgTitle . setAttribute ( 'content' , ogTitle ) ;
64- } else {
65- const newMeta = document . createElement ( 'meta' ) ;
66- newMeta . setAttribute ( 'property' , 'og:title' ) ;
67- newMeta . setAttribute ( 'content' , ogTitle ) ;
68- document . head . appendChild ( newMeta ) ;
69- }
70- }
60+ const finalOgImage = toAbsoluteUrl ( ogImagePath ) ;
61+ const finalTwitterImage = toAbsoluteUrl ( twitterImagePath ) ;
7162
72- if ( ogDescription ) {
73- const metaOgDescription = document . querySelector (
74- 'meta[property="og:description"]' ,
75- ) ;
76- if ( metaOgDescription ) {
77- metaOgDescription . setAttribute ( 'content' , ogDescription ) ;
78- } else {
79- const newMeta = document . createElement ( 'meta' ) ;
80- newMeta . setAttribute ( 'property' , 'og:description' ) ;
81- newMeta . setAttribute ( 'content' , ogDescription ) ;
82- document . head . appendChild ( newMeta ) ;
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+ } ;
72+
73+ Object . entries ( ogTags ) . forEach ( ( [ prop , content ] ) => {
74+ if ( ! content ) return ;
75+ let el = document . querySelector ( `meta[property="${ prop } "]` ) ;
76+ if ( ! el ) {
77+ el = document . createElement ( 'meta' ) ;
78+ el . setAttribute ( 'property' , prop ) ;
79+ document . head . appendChild ( el ) ;
8380 }
84- }
85-
86- if ( finalOgImage ) {
87- const metaOgImage = document . querySelector ( 'meta[property="og:image"]' ) ;
88- if ( metaOgImage ) {
89- metaOgImage . setAttribute ( 'content' , finalOgImage ) ;
90- } else {
91- const newMeta = document . createElement ( 'meta' ) ;
92- newMeta . setAttribute ( 'property' , 'og:image' ) ;
93- newMeta . setAttribute ( 'content' , finalOgImage ) ;
94- document . head . appendChild ( newMeta ) ;
81+ 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 ) ;
9590 }
91+ el . setAttribute ( 'content' , finalOgImage ) ;
9692 }
9793
9894 // Set Twitter card meta tags
99- if ( twitterCard ) {
100- const metaTwitterCard = document . querySelector (
101- 'meta[name="twitter:card"]' ,
102- ) ;
103- if ( metaTwitterCard ) {
104- metaTwitterCard . setAttribute ( 'content' , twitterCard ) ;
105- } else {
106- const newMeta = document . createElement ( 'meta' ) ;
107- newMeta . setAttribute ( 'name' , 'twitter:card' ) ;
108- newMeta . setAttribute ( 'content' , twitterCard ) ;
109- document . head . appendChild ( newMeta ) ;
110- }
111- }
112-
113- if ( twitterTitle ) {
114- const metaTwitterTitle = document . querySelector (
115- 'meta[name="twitter:title"]' ,
116- ) ;
117- if ( metaTwitterTitle ) {
118- metaTwitterTitle . setAttribute ( 'content' , twitterTitle ) ;
119- } else {
120- const newMeta = document . createElement ( 'meta' ) ;
121- newMeta . setAttribute ( 'name' , 'twitter:title' ) ;
122- newMeta . setAttribute ( 'content' , twitterTitle ) ;
123- document . head . appendChild ( newMeta ) ;
124- }
125- }
126-
127- if ( twitterDescription ) {
128- const metaTwitterDescription = document . querySelector (
129- 'meta[name="twitter:description"]' ,
130- ) ;
131- if ( metaTwitterDescription ) {
132- metaTwitterDescription . setAttribute ( 'content' , twitterDescription ) ;
133- } else {
134- const newMeta = document . createElement ( 'meta' ) ;
135- newMeta . setAttribute ( 'name' , 'twitter:description' ) ;
136- newMeta . setAttribute ( 'content' , twitterDescription ) ;
137- document . head . appendChild ( newMeta ) ;
138- }
139- }
140-
141- if ( finalTwitterImage ) {
142- const metaTwitterImage = document . querySelector (
143- 'meta[name="twitter:image"]' ,
144- ) ;
145- if ( metaTwitterImage ) {
146- metaTwitterImage . setAttribute ( 'content' , finalTwitterImage ) ;
147- } else {
148- const newMeta = document . createElement ( 'meta' ) ;
149- newMeta . setAttribute ( 'name' , 'twitter:image' ) ;
150- newMeta . setAttribute ( 'content' , finalTwitterImage ) ;
151- document . head . appendChild ( newMeta ) ;
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 ,
101+ } ;
102+
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 ) ;
152110 }
153- }
154-
155- // Update URL meta tags (Canonical and OG URL)
156- const currentUrl = window . location . origin + location . pathname ;
157-
158- // OG URL
159- const metaOgUrl = document . querySelector ( 'meta[property="og:url"]' ) ;
160- if ( metaOgUrl ) {
161- metaOgUrl . setAttribute ( 'content' , currentUrl ) ;
162- } else {
163- const newMeta = document . createElement ( 'meta' ) ;
164- newMeta . setAttribute ( 'property' , 'og:url' ) ;
165- newMeta . setAttribute ( 'content' , currentUrl ) ;
166- document . head . appendChild ( newMeta ) ;
167- }
168-
169- // Twitter URL
170- const metaTwitterUrl = document . querySelector ( 'meta[name="twitter:url"]' ) ;
171- if ( metaTwitterUrl ) {
172- metaTwitterUrl . setAttribute ( 'content' , currentUrl ) ;
173- } else {
174- const newMeta = document . createElement ( 'meta' ) ;
175- newMeta . setAttribute ( 'name' , 'twitter:url' ) ;
176- newMeta . setAttribute ( 'content' , currentUrl ) ;
177- document . head . appendChild ( newMeta ) ;
178- }
111+ el . setAttribute ( 'content' , content ) ;
112+ } ) ;
179113
180114 // Canonical link
181115 let canonicalLink = document . querySelector ( 'link[rel="canonical"]' ) ;
182- if ( canonicalLink ) {
183- canonicalLink . setAttribute ( 'href' , currentUrl ) ;
184- } else {
116+ if ( ! canonicalLink ) {
185117 canonicalLink = document . createElement ( 'link' ) ;
186- canonicalLink . setAttribute ( 'rel' , 'canonical' ) ;
187- canonicalLink . setAttribute ( 'href' , currentUrl ) ;
188- document . head . appendChild ( canonicalLink ) ;
189- }
190-
191- return ( ) => {
192- // Cleanup: Restore defaults from index.html on unmount
193- const defaults = {
194- title : 'fezcodex' ,
195- description : 'codex by fezcode...' ,
196- ogTitle : 'Fezcodex - Personal Blog and Projects' ,
197- ogDescription :
198- 'Discover logs, posts, projects, and stories from Fezcode.' ,
199- ogImage : '/images/asset/ogtitle.png' ,
200- ogUrl : 'https://fezcode.com/' ,
201- twitterCard : 'summary_large_image' ,
202- twitterTitle : 'Fezcodex - Personal Blog and Projects' ,
203- twitterDescription :
204- 'Discover logs, posts, projects, and stories from Fezcode.' ,
205- twitterImage : '/images/asset/ogtitle.png' ,
206- twitterUrl : 'https://fezcode.com/' ,
207- } ;
208-
209- document . title = defaults . title ;
210-
211- const metaDescription = document . querySelector (
212- 'meta[name="description"]' ,
213- ) ;
214- if ( metaDescription )
215- metaDescription . setAttribute ( 'content' , defaults . description ) ;
118+ canonicalLink . setAttribute ( 'rel' , 'canonical' ) ;
119+ document . head . appendChild ( canonicalLink ) ;
120+ }
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 ) ;
216143
217- const metaOgTitle = document . querySelector ( 'meta[property="og:title"]' ) ;
218- if ( metaOgTitle ) metaOgTitle . setAttribute ( 'content' , defaults . ogTitle ) ;
144+ const metaOgTitle = document . querySelector ( 'meta[property="og:title"]' ) ;
145+ if ( metaOgTitle ) metaOgTitle . setAttribute ( 'content' , defaults . ogTitle ) ;
219146
220- const metaOgDescription = document . querySelector (
221- 'meta[property="og:description"]' ,
222- ) ;
223- if ( metaOgDescription )
224- metaOgDescription . setAttribute ( 'content' , defaults . ogDescription ) ;
147+ const metaOgDescription = document . querySelector ( 'meta[property="og:description"]' ) ;
148+ if ( metaOgDescription ) metaOgDescription . setAttribute ( 'content' , defaults . ogDescription ) ;
225149
226- const metaOgImage = document . querySelector ( 'meta[property="og:image"]' ) ;
227- if ( metaOgImage ) metaOgImage . setAttribute ( 'content' , defaults . ogImage ) ;
150+ const metaOgImage = document . querySelector ( 'meta[property="og:image"]' ) ;
151+ if ( metaOgImage ) metaOgImage . setAttribute ( 'content' , defaults . ogImage ) ;
228152
229- const metaOgUrl = document . querySelector ( 'meta[property="og:url"]' ) ;
230- if ( metaOgUrl ) metaOgUrl . setAttribute ( 'content' , defaults . ogUrl ) ;
153+ const metaOgUrl = document . querySelector ( 'meta[property="og:url"]' ) ;
154+ if ( metaOgUrl ) metaOgUrl . setAttribute ( 'content' , defaults . ogUrl ) ;
231155
232- const metaTwitterCard = document . querySelector (
233- 'meta[name="twitter:card"]' ,
234- ) ;
235- if ( metaTwitterCard )
236- metaTwitterCard . setAttribute ( 'content' , defaults . twitterCard ) ;
156+ const metaTwitterCard = document . querySelector ( 'meta[name="twitter:card"]' ) ;
157+ if ( metaTwitterCard ) metaTwitterCard . setAttribute ( 'content' , defaults . twitterCard ) ;
237158
238- const metaTwitterTitle = document . querySelector (
239- 'meta[name="twitter:title"]' ,
240- ) ;
241- if ( metaTwitterTitle )
242- metaTwitterTitle . setAttribute ( 'content' , defaults . twitterTitle ) ;
159+ const metaTwitterTitle = document . querySelector ( 'meta[name="twitter:title"]' ) ;
160+ if ( metaTwitterTitle ) metaTwitterTitle . setAttribute ( 'content' , defaults . twitterTitle ) ;
243161
244- const metaTwitterDescription = document . querySelector (
245- 'meta[name="twitter:description"]' ,
246- ) ;
247- if ( metaTwitterDescription )
248- metaTwitterDescription . setAttribute ( 'content' , defaults . twitterDescription ) ;
162+ const metaTwitterDescription = document . querySelector ( 'meta[name="twitter:description"]' ) ;
163+ if ( metaTwitterDescription ) metaTwitterDescription . setAttribute ( 'content' , defaults . twitterDescription ) ;
249164
250- const metaTwitterImage = document . querySelector (
251- 'meta[name="twitter:image"]' ,
252- ) ;
253- if ( metaTwitterImage )
254- metaTwitterImage . setAttribute ( 'content' , defaults . twitterImage ) ;
165+ const metaTwitterImage = document . querySelector ( 'meta[name="twitter:image"]' ) ;
166+ if ( metaTwitterImage ) metaTwitterImage . setAttribute ( 'content' , defaults . twitterImage ) ;
255167
256- const metaTwitterUrl = document . querySelector ( 'meta[name="twitter:url"]' ) ;
257- if ( metaTwitterUrl )
258- metaTwitterUrl . setAttribute ( 'content' , defaults . twitterUrl ) ;
168+ const metaTwitterUrl = document . querySelector ( 'meta[name="twitter:url"]' ) ;
169+ if ( metaTwitterUrl ) metaTwitterUrl . setAttribute ( 'content' , defaults . twitterUrl ) ;
259170
260- const canonicalLink = document . querySelector ( 'link[rel="canonical"]' ) ;
261- if ( canonicalLink ) canonicalLink . setAttribute ( 'href' , defaults . ogUrl ) ;
262- } ;
263- } , [
264- location . pathname ,
171+ const canonicalLink = document . querySelector ( 'link[rel="canonical"]' ) ;
172+ if ( canonicalLink ) canonicalLink . setAttribute ( 'href' , defaults . ogUrl ) ;
173+ } ;
174+ } , [
175+ location . pathname ,
265176 title ,
266177 description ,
267178 keywords ,
@@ -275,4 +186,4 @@ function useSeo({
275186 ] ) ;
276187}
277188
278- export default useSeo ;
189+ export default useSeo ;
0 commit comments