22import ' ../styles/global.css' ;
33import type { ImageMetadata } from ' astro' ;
44import FallbackImage from ' ../assets/qveris-brand.png' ;
5- import { SITE_TITLE } from ' ../consts' ;
5+ import { SITE_TITLE , SITE_URL } from ' ../consts' ;
6+ import { locales , defaultLocale } from ' ../i18n/config' ;
7+ import type { Locale } from ' ../i18n/config' ;
8+ import { switchLocaleInPath , getHtmlLang } from ' ../i18n/utils' ;
69
710interface Props {
811 title: string ;
912 description: string ;
1013 image? : ImageMetadata ;
14+ // SEO enhancements
15+ lang? : Locale ;
16+ ogType? : ' website' | ' article' ;
17+ pubDate? : Date ;
18+ updatedDate? : Date ;
19+ author? : string ;
20+ tags? : string [];
1121}
1222
13- const canonicalURL = new URL (Astro .url .pathname , Astro .site );
23+ const {
24+ title,
25+ description,
26+ image = FallbackImage ,
27+ lang = ' en' ,
28+ ogType = ' website' ,
29+ pubDate,
30+ updatedDate,
31+ author,
32+ tags,
33+ } = Astro .props ;
1434
15- const { title, description, image = FallbackImage } = Astro .props ;
35+ // Use Astro.site with SITE_URL fallback for all URL construction
36+ const site = Astro .site ?? new URL (SITE_URL );
37+ const canonicalURL = new URL (Astro .url .pathname , site );
38+ const ogLocale = lang === ' cn' ? ' zh_CN' : ' en_US' ;
39+ const imageUrl = new URL (image .src , Astro .url );
40+
41+ // hreflang: dynamically generate alternate links for all supported locales
42+ const pathname = Astro .url .pathname ;
43+ const hreflangLinks = locales .map ((loc ) => ({
44+ hreflang: getHtmlLang (loc ),
45+ href: new URL (switchLocaleInPath (pathname , loc ), site ),
46+ }));
47+ const defaultUrl = new URL (switchLocaleInPath (pathname , defaultLocale ), site );
1648---
1749
1850<meta charset =" utf-8" />
1951<meta name =" viewport" content =" width=device-width,initial-scale=1" />
2052<link rel =" icon" type =" image/svg+xml" href =" /favicon.svg" />
2153<link rel =" icon" href =" /favicon.ico" />
2254<link rel =" sitemap" href =" /sitemap-index.xml" />
23- <link rel =" alternate" type =" application/rss+xml" title ={ SITE_TITLE } href ={ new URL (' rss.xml' , Astro . site )} />
55+ <link rel =" alternate" type =" application/rss+xml" title ={ SITE_TITLE } href ={ new URL (' rss.xml' , site )} />
2456<meta name =" generator" content ={ Astro .generator } />
2557
2658<link rel =" preconnect" href =" https://fonts.googleapis.com" />
@@ -32,20 +64,46 @@ const { title, description, image = FallbackImage } = Astro.props;
3264
3365<meta name =" theme-color" content =" #05070B" />
3466
67+ <!-- Canonical & hreflang -->
3568<link rel =" canonical" href ={ canonicalURL } />
69+ { hreflangLinks .map (({ hreflang , href }) => (
70+ <link rel = " alternate" hreflang = { hreflang } href = { href } />
71+ ))}
72+ <link rel =" alternate" hreflang =" x-default" href ={ defaultUrl } />
3673
74+ <!-- Primary meta -->
3775<title >{ title } </title >
38- <meta name =" title" content ={ title } />
3976<meta name =" description" content ={ description } />
77+ { author && <meta name = " author" content = { author } />}
4078
41- <meta property =" og:type" content =" website" />
79+ <!-- Open Graph -->
80+ <meta property =" og:type" content ={ ogType } />
4281<meta property =" og:url" content ={ Astro .url } />
4382<meta property =" og:title" content ={ title } />
4483<meta property =" og:description" content ={ description } />
45- <meta property =" og:image" content ={ new URL (image .src , Astro .url )} />
84+ <meta property =" og:image" content ={ imageUrl } />
85+ <meta property =" og:image:width" content ={ String (image .width )} />
86+ <meta property =" og:image:height" content ={ String (image .height )} />
87+ <meta property =" og:image:alt" content ={ title } />
88+ <meta property =" og:locale" content ={ ogLocale } />
89+ <meta property =" og:site_name" content ={ SITE_TITLE } />
90+
91+ { ogType === ' article' && pubDate && (
92+ <meta property = " article:published_time" content = { pubDate .toISOString ()} />
93+ )}
94+ { ogType === ' article' && updatedDate && (
95+ <meta property = " article:modified_time" content = { updatedDate .toISOString ()} />
96+ )}
97+ { ogType === ' article' && author && (
98+ <meta property = " article:author" content = { author } />
99+ )}
100+ { ogType === ' article' && tags ?.map ((tag ) => (
101+ <meta property = " article:tag" content = { tag } />
102+ ))}
46103
47- <meta property =" twitter:card" content =" summary_large_image" />
48- <meta property =" twitter:url" content ={ Astro .url } />
49- <meta property =" twitter:title" content ={ title } />
50- <meta property =" twitter:description" content ={ description } />
51- <meta property =" twitter:image" content ={ new URL (image .src , Astro .url )} />
104+ <!-- Twitter Card -->
105+ <meta name =" twitter:card" content =" summary_large_image" />
106+ <meta name =" twitter:url" content ={ Astro .url } />
107+ <meta name =" twitter:title" content ={ title } />
108+ <meta name =" twitter:description" content ={ description } />
109+ <meta name =" twitter:image" content ={ imageUrl } />
0 commit comments