diff --git a/site/components/common/Head/Head.tsx b/site/components/common/Head/Head.tsx
index b2c0c997b..ed43c0d39 100644
--- a/site/components/common/Head/Head.tsx
+++ b/site/components/common/Head/Head.tsx
@@ -1,17 +1,16 @@
-import { FC } from 'react'
-import NextHead from 'next/head'
-import { DefaultSeo } from 'next-seo'
-import config from '@config/seo.json'
+import type { VFC } from 'react'
+import { SEO } from '@components/common'
-const Head: FC = () => {
+const Head: VFC = () => {
return (
- <>
-
-
-
-
-
- >
+
+
+
+
)
}
diff --git a/site/components/common/SEO/SEO.tsx b/site/components/common/SEO/SEO.tsx
new file mode 100644
index 000000000..b5e3ad23b
--- /dev/null
+++ b/site/components/common/SEO/SEO.tsx
@@ -0,0 +1,157 @@
+import Head from 'next/head'
+import { FC, Fragment, ReactNode } from 'react'
+import config from '@config/seo_meta.json'
+
+const storeUrl =
+ process.env.NEXT_PUBLIC_STORE_URL || process.env.NEXT_PUBLIC_VERCEL_URL
+const storeBaseUrl = storeUrl ? `https://${storeUrl}` : null
+
+interface OgImage {
+ url?: string
+ width?: string
+ height?: string
+ alt?: string
+}
+
+interface Props {
+ title?: string
+ description?: string
+ robots?: string
+ openGraph?: {
+ title?: string
+ type?: string
+ locale?: string
+ description?: string
+ site_name?: string
+ url?: string
+ images?: OgImage[]
+ }
+ children?: ReactNode
+}
+
+const ogImage = ({ url, width, height, alt }: OgImage, index: number) => {
+ // generate full URL for OG image url with store base URL
+ const imgUrl = storeBaseUrl ? new URL(url!, storeBaseUrl).toString() : url
+ return (
+
+
+
+
+
+
+ )
+}
+
+const SEO: FC = ({
+ title,
+ description,
+ openGraph,
+ robots,
+ children,
+}) => {
+ /**
+ * @see https://nextjs.org/docs/api-reference/next/head
+ *
+ * meta or any other elements need to be contained as direct children of the Head element,
+ * or wrapped into maximum one level of or arrays
+ * otherwise the tags won't be correctly picked up on client-side navigations.
+ *
+ * The `key` property makes the tag is only rendered once,
+ */
+ return (
+
+
+ {title ? `${config.titleTemplate.replace(/%s/g, title)}` : config.title}
+
+
+
+
+
+
+
+ {openGraph?.locale && (
+
+ )}
+ {openGraph?.images?.length
+ ? openGraph.images.map((img, index) => ogImage(img, index))
+ : ogImage(config.openGraph.images[0], 0)}
+ {config.twitter.cardType && (
+
+ )}
+ {config.twitter.site && (
+
+ )}
+ {config.twitter.handle && (
+
+ )}
+
+
+ {children}
+
+ )
+}
+
+export default SEO
diff --git a/site/components/common/SEO/index.ts b/site/components/common/SEO/index.ts
new file mode 100644
index 000000000..e20ec8600
--- /dev/null
+++ b/site/components/common/SEO/index.ts
@@ -0,0 +1 @@
+export { default } from './SEO'
diff --git a/site/components/common/index.ts b/site/components/common/index.ts
index 98dd3394b..054110c75 100644
--- a/site/components/common/index.ts
+++ b/site/components/common/index.ts
@@ -7,3 +7,4 @@ export { default as Searchbar } from './Searchbar'
export { default as UserNav } from './UserNav'
export { default as Head } from './Head'
export { default as I18nWidget } from './I18nWidget'
+export { default as SEO } from './SEO'
diff --git a/site/components/product/ProductView/ProductView.tsx b/site/components/product/ProductView/ProductView.tsx
index b20c85b0a..31cbcd577 100644
--- a/site/components/product/ProductView/ProductView.tsx
+++ b/site/components/product/ProductView/ProductView.tsx
@@ -1,6 +1,5 @@
import cn from 'clsx'
import Image from 'next/image'
-import { NextSeo } from 'next-seo'
import s from './ProductView.module.css'
import { FC } from 'react'
import type { Product } from '@commerce/types/product'
@@ -8,6 +7,7 @@ import usePrice from '@framework/product/use-price'
import { WishlistButton } from '@components/wishlist'
import { ProductSlider, ProductCard } from '@components/product'
import { Container, Text } from '@components/ui'
+import { SEO } from '@components/common'
import ProductSidebar from '../ProductSidebar'
import ProductTag from '../ProductTag'
interface ProductViewProps {
@@ -89,7 +89,7 @@ const ProductView: FC = ({ product, relatedProducts }) => {
- = ({ product, relatedProducts }) => {
images: [
{
url: product.images[0]?.url!,
- width: 800,
- height: 600,
+ width: '800',
+ height: '600',
alt: product.name,
},
],
diff --git a/site/config/seo.json b/site/config/seo_meta.json
similarity index 90%
rename from site/config/seo.json
rename to site/config/seo_meta.json
index 82520cf9b..c9c5fc226 100644
--- a/site/config/seo.json
+++ b/site/config/seo_meta.json
@@ -6,14 +6,13 @@
"title": "ACME Storefront | Powered by Next.js Commerce",
"description": "Next.js Commerce - https://www.nextjs.org/commerce",
"type": "website",
- "locale": "en_IE",
"url": "https://nextjs.org/commerce",
"site_name": "Next.js Commerce",
"images": [
{
"url": "/card.png",
- "width": 800,
- "height": 600,
+ "width": "800",
+ "height": "600",
"alt": "Next.js Commerce"
}
]
diff --git a/site/package.json b/site/package.json
index 7a28e7e7b..f5b38acfd 100644
--- a/site/package.json
+++ b/site/package.json
@@ -34,7 +34,6 @@
"lodash.random": "^3.2.0",
"lodash.throttle": "^4.1.1",
"next": "^12.0.8",
- "next-seo": "^4.28.1",
"next-themes": "^0.0.15",
"postcss": "^8.3.5",
"postcss-nesting": "^8.0.1",
diff --git a/yarn.lock b/yarn.lock
index 1c1a7fc7b..302dd2402 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4763,11 +4763,6 @@ natural-compare@^1.4.0:
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=
-next-seo@^4.28.1:
- version "4.29.0"
- resolved "https://registry.yarnpkg.com/next-seo/-/next-seo-4.29.0.tgz#d281e95ba47914117cc99e9e468599f0547d9b9b"
- integrity sha512-xmwzcz4uHaYJ8glbuhs6FSBQ7z3irmdPYdJJ5saWm72Uy3o+mPKGaPCXQetTCE6/xxVnpoDV4yFtFlEjUcljSg==
-
next-themes@^0.0.15:
version "0.0.15"
resolved "https://registry.yarnpkg.com/next-themes/-/next-themes-0.0.15.tgz#ab0cee69cd763b77d41211f631e108beab39bf7d"