/ shared / components / src / components / MetaTags / MetaTags.svelte
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>