MetaTags.svelte
1 <script lang="ts"> 2 import { LTR_MARK, RTL_MARK } from '@amp/web-app-components/src/constants'; 3 import type { Locale } from '@amp/web-app-components/src/types'; 4 import type { 5 SeoData, 6 HreflangTag, 7 } from '@amp/web-app-components/src/components/MetaTags/types'; 8 import type { ImageURLParams } from '@amp/web-app-components/src/components/Artwork/types'; 9 import { buildSrcSeo } from '@amp/web-app-components/src/components/Artwork/utils/srcset'; 10 import { serializeJSONData } from '@amp/web-app-components/src/utils/sanitize'; 11 12 export let seoData: SeoData | undefined = undefined; 13 export let locale: Locale; 14 export let origin: string; 15 export let pageDir: string; 16 export let defaultTitle: string; 17 export let hreflangTags: HreflangTag[] | null = null; 18 19 // Music's Classical Bridge prefers to use a different canonical 20 // for rel=canonical tags than the page url. Uses page url as fallback. 21 $: canonicalUrl = seoData?.canonicalUrl ?? seoData?.url; 22 $: pageTitle = seoData?.pageTitle ?? defaultTitle; 23 $: formattedLocale = locale.language.replace(/-/g, '_') || null; 24 $: directionMarker = pageDir === 'rtl' ? RTL_MARK : LTR_MARK; 25 26 function processSocialImage( 27 artworkUrl: string, 28 imgParams: ImageURLParams, 29 ): string | undefined { 30 if (artworkUrl.startsWith('/')) { 31 artworkUrl = `${origin}${artworkUrl}`; 32 } 33 return buildSrcSeo(artworkUrl, imgParams); 34 } 35 36 $: ogImageUrl = !!seoData?.artworkUrl 37 ? processSocialImage(seoData.artworkUrl, { 38 width: seoData.width, 39 height: seoData.height, 40 crop: seoData.crop, 41 fileType: seoData.fileType, 42 quality: seoData.quality, 43 }) 44 : null; 45 $: twitterImageUrl = !!seoData?.artworkUrl 46 ? processSocialImage(seoData.artworkUrl, { 47 width: seoData.twitterWidth, 48 height: seoData.twitterHeight, 49 crop: seoData.twitterCropCode, 50 fileType: seoData.fileType, 51 quality: seoData.quality, 52 }) 53 : null; 54 55 $: sanitizedSchemaContent = !!seoData?.schemaContent 56 ? serializeJSONData(seoData.schemaContent) 57 : null; 58 59 $: sanitizedBreadcrumbSchemaContent = !!seoData?.breadcrumbSchemaContent 60 ? serializeJSONData(seoData.breadcrumbSchemaContent) 61 : null; 62 </script> 63 64 <svelte:head> 65 {#if pageTitle} 66 <!--directionMarker forces the direction so we don't get "....More from "some rtl text""--> 67 <title>{directionMarker}{pageTitle}</title> 68 {/if} 69 70 {#if !!seoData} 71 <!-- Begin General --> 72 <!-- NOTE: If configuring robots tags, use one of these options, but not both --> 73 {#if seoData.noFollow} 74 <!-- Use this when you do not want your page indexed or your links followed --> 75 <meta name="robots" content="noindex, nofollow" /> 76 {:else if seoData.noIndex} 77 <!-- Use this when you want your links followed but not have the page indexed --> 78 <meta name="robots" content="noindex" /> 79 {/if} 80 81 {#if seoData.description} 82 <meta name="description" content={seoData.description} /> 83 {/if} 84 85 {#if seoData.keywords} 86 <meta name="keywords" content={seoData.keywords} /> 87 {/if} 88 89 {#if canonicalUrl} 90 <link rel="canonical" href={canonicalUrl} /> 91 {/if} 92 93 {#if hreflangTags} 94 {#each hreflangTags as langTag} 95 {#if langTag} 96 <link 97 rel="alternate" 98 href={langTag.path} 99 hreflang={langTag.tag} 100 /> 101 {/if} 102 {/each} 103 {/if} 104 <!-- End General --> 105 106 {#if !!seoData.oembedData?.url} 107 <link 108 rel="alternate" 109 type="application/json+oembed" 110 href={`${origin}/api/oembed?url=${encodeURIComponent( 111 seoData.oembedData.url, 112 )}`} 113 title={seoData.oembedData.title ?? ''} 114 /> 115 {/if} 116 117 <!-- Begin Apple-specific meta tags --> 118 {#if seoData.appleStoreId} 119 <meta name="al:ios:app_store_id" content={seoData.appleStoreId} /> 120 {/if} 121 122 {#if seoData.appleStoreName} 123 <meta name="al:ios:app_name" content={seoData.appleStoreName} /> 124 {/if} 125 126 {#if seoData.appleContentId} 127 <meta name="apple:content_id" content={seoData.appleContentId} /> 128 {/if} 129 130 {#if seoData.appleTitle} 131 <meta name="apple:title" content={seoData.appleTitle} /> 132 {/if} 133 134 {#if seoData.appleDescription} 135 <meta name="apple:description" content={seoData.appleDescription} /> 136 {/if} 137 <!-- End Apple-specific meta tags --> 138 139 <!-- Begin OpenGraph (FaceBook, Slack, etc) --> 140 {#if seoData.socialTitle} 141 <meta property="og:title" content={seoData.socialTitle} /> 142 {/if} 143 144 {#if seoData.socialDescription} 145 <meta 146 property="og:description" 147 content={seoData.socialDescription} 148 /> 149 {/if} 150 151 {#if seoData.siteName} 152 <meta property="og:site_name" content={seoData.siteName} /> 153 {/if} 154 155 {#if seoData.url} 156 <meta property="og:url" content={seoData.url} /> 157 {/if} 158 159 {#if ogImageUrl} 160 <meta property="og:image" content={ogImageUrl} /> 161 <meta property="og:image:secure_url" content={ogImageUrl} /> 162 163 {#if seoData.imageAltTitle} 164 <meta property="og:image:alt" content={seoData.imageAltTitle} /> 165 {:else if seoData.socialTitle} 166 <meta property="og:image:alt" content={seoData.socialTitle} /> 167 {/if} 168 169 {#if seoData.width} 170 <meta 171 property="og:image:width" 172 content={seoData.width.toString()} 173 /> 174 {/if} 175 176 {#if seoData.height} 177 <meta 178 property="og:image:height" 179 content={seoData.height.toString()} 180 /> 181 {/if} 182 183 {#if seoData.fileType} 184 <meta 185 property="og:image:type" 186 content={`image/${seoData.fileType}`} 187 /> 188 {/if} 189 {/if} 190 191 {#if seoData.ogType} 192 <meta property="og:type" content={seoData.ogType} /> 193 {/if} 194 195 {#if seoData.socialTitle && formattedLocale} 196 <meta property="og:locale" content={formattedLocale} /> 197 {/if} 198 199 {#if $$slots['extendedOpenGraphData']} 200 <slot name="extendedOpenGraphData" /> 201 {/if} 202 <!-- End OpenGraph --> 203 204 <!-- Begin Twitter --> 205 {#if seoData.socialTitle} 206 <meta name="twitter:title" content={seoData.socialTitle} /> 207 {/if} 208 209 {#if seoData.socialDescription} 210 <meta 211 name="twitter:description" 212 content={seoData.socialDescription} 213 /> 214 {/if} 215 216 {#if seoData.twitterSite} 217 <meta name="twitter:site" content={seoData.twitterSite} /> 218 {/if} 219 220 {#if twitterImageUrl} 221 <meta name="twitter:image" content={twitterImageUrl} /> 222 223 {#if seoData.imageAltTitle} 224 <meta 225 name="twitter:image:alt" 226 content={seoData.imageAltTitle} 227 /> 228 {:else if seoData.socialTitle} 229 <meta name="twitter:image:alt" content={seoData.socialTitle} /> 230 {/if} 231 {/if} 232 233 {#if seoData.twitterCardType} 234 <meta name="twitter:card" content={seoData.twitterCardType} /> 235 {/if} 236 <!-- End Twitter --> 237 238 <!-- Begin schema.org --> 239 {#if $$slots['schemaOrganizationData']} 240 <slot name="schemaOrganizationData" /> 241 {/if} 242 243 {#if seoData.schemaName && sanitizedSchemaContent} 244 {@html ` 245 <script id=${seoData.schemaName} type="application/ld+json"> 246 ${sanitizedSchemaContent} 247 </script> 248 `} 249 {/if} 250 <!-- End schema.org --> 251 252 <!-- Begin breadcrumb schema --> 253 {#if seoData.breadcrumbSchemaName && sanitizedBreadcrumbSchemaContent} 254 {@html ` 255 <script id=${seoData.breadcrumbSchemaName} name=${seoData.breadcrumbSchemaName} type="application/ld+json"> 256 ${sanitizedBreadcrumbSchemaContent} 257 </script> 258 `} 259 {/if} 260 <!-- End breadcrumb schema --> 261 {/if} 262 </svelte:head>