Source: SEO/index.tsx

  1. import { useRouter } from 'next/router';
  2. import React from 'react';
  3. import { author as auth } from '@/constants/site';
  4. import { getLang, cleanTrailingSlash } from '@/helpers';
  5. import Head from 'next/head';
  6. type Props = {
  7. isBlog?: boolean;
  8. noimage?: boolean;
  9. meta?: {
  10. noindex?: boolean;
  11. title: string;
  12. author?: string;
  13. description?: string;
  14. image?: string;
  15. category?: string;
  16. alternate?: Array<{ lang: string; url: string }>;
  17. slug?: string;
  18. url?: string;
  19. };
  20. };
  21. /**
  22. * @example
  23. * <SEO meta={meta} isBlog={true} />;
  24. *
  25. * @param {object} meta - The object containing the meta data for SEO
  26. * @param {boolean} isBlog - Whether the page is a blog post the SEO changes
  27. * @param {boolean} noimage - Whether to show the image in the SEO
  28. * @returns {JSX.Element}
  29. */
  30. const SEO = ({ meta, isBlog, noimage = true }: Props) => {
  31. const { locale: l, pathname: path } = useRouter();
  32. const category = meta?.category?.toLowerCase();
  33. const url = isBlog
  34. ? `${process.env.NEXT_PUBLIC_DOMAIN}${getLang(l)}/blog/${category}/${meta?.slug}`
  35. : `${process.env.NEXT_PUBLIC_DOMAIN}${getLang(l)}${cleanTrailingSlash(path)}`;
  36. const title = meta?.title;
  37. const author = meta?.author || auth;
  38. const description = meta?.description;
  39. const image = meta?.image ?? '/profile.png';
  40. return (
  41. <>
  42. <Head>
  43. {isBlog ? (
  44. <script
  45. data-testid="json-ld"
  46. type="application/ld+json"
  47. key="item-jsonld"
  48. dangerouslySetInnerHTML={{
  49. __html: JSON.stringify({
  50. '@context': 'https://schema.org',
  51. '@type': 'Article',
  52. headline: title,
  53. description: description,
  54. url: url,
  55. ...(image && { image: [`${process.env.NEXT_PUBLIC_DOMAIN}/${image}`] }),
  56. datePublished: new Date().toISOString(),
  57. dateModified: new Date().toISOString(),
  58. author: [
  59. {
  60. '@type': 'Person',
  61. name: author,
  62. url: process.env.NEXT_PUBLIC_DOMAIN,
  63. },
  64. ],
  65. }),
  66. }}
  67. />
  68. ) : (
  69. <>
  70. <link
  71. hrefLang="es"
  72. rel="alternate"
  73. href={`${process.env.NEXT_PUBLIC_DOMAIN}/es${cleanTrailingSlash(path)}`}
  74. />
  75. <link
  76. hrefLang="gl"
  77. rel="alternate"
  78. href={`${process.env.NEXT_PUBLIC_DOMAIN}/gl${cleanTrailingSlash(path)}`}
  79. />
  80. </>
  81. )}
  82. <title>{title}</title>
  83. {meta?.noindex ? (
  84. <>
  85. <meta name="robots" content="noindex" />
  86. <meta name="googlebot" content="noindex" />
  87. </>
  88. ) : (
  89. <>
  90. <meta name="robots" content="index,follow" />
  91. <meta name="googlebot" content="index,follow" />
  92. </>
  93. )}
  94. <meta name="author" content={author} />
  95. <meta name="description" content={description} />
  96. <meta property="og:description" content={description} />
  97. <meta name="twitter:description" content={description} />
  98. <meta property="og:title" content={title} />
  99. <meta name="twitter:title" content={title} />
  100. {noimage && (
  101. <>
  102. <meta name="image" content={image} />
  103. <meta property="og:image" content={`${process.env.NEXT_PUBLIC_DOMAIN}${image}`} />
  104. </>
  105. )}
  106. <meta property="og:url" content={url} />
  107. <link rel="canonical" href={url} title="Canonical url" />
  108. {meta?.alternate?.map(({ lang, url }, index) => (
  109. <link
  110. data-testid="blog-alternate"
  111. key={index}
  112. rel="alternate"
  113. href={`${process.env.NEXT_PUBLIC_DOMAIN}${getLang(lang)}/blog/${category}/${url}`}
  114. hrefLang={lang}
  115. title={`Alternate url for langueage ${lang}`}
  116. />
  117. ))}
  118. </Head>
  119. </>
  120. );
  121. };
  122. export default SEO;