Skip to content

Commit 00d7051

Browse files
authored
Merge pull request #38 from imranhsayed/feature/add-yoast-seo
Add Yoast Seo and Schema
2 parents 9f532b2 + deba08e commit 00d7051

File tree

8 files changed

+178
-7
lines changed

8 files changed

+178
-7
lines changed

package-lock.json

+17
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"lodash": "^4.17.21",
1919
"micro": "^9.4.1",
2020
"next": "^12.3.0",
21+
"next-seo": "^5.15.0",
2122
"next-stripe": "^1.0.0-beta.9",
2223
"prop-types": "^15.8.1",
2324
"react": "^18.2.0",

pages/index.js

+11-1
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,18 @@ import { getProductsData } from '../src/utils/products';
1212
import Layout from '../src/components/layout';
1313

1414
export default function Home({ headerFooter, products }) {
15+
const seo = {
16+
title: 'Next JS WooCommerce REST API',
17+
description: 'Next JS WooCommerce Theme',
18+
og_image: [],
19+
og_site_name: 'React WooCommerce Theme',
20+
robots: {
21+
index: 'index',
22+
follow: 'follow',
23+
},
24+
}
1525
return (
16-
<Layout headerFooter={headerFooter || {}}>
26+
<Layout headerFooter={ headerFooter || {} } seo={ seo }>
1727
<Products products={products}/>
1828
</Layout>
1929
)

pages/product/[slug].js

+5-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,11 @@ export default function Product( { headerFooter, product } ) {
2323
}
2424

2525
return (
26-
<Layout headerFooter={ headerFooter || {} }>
26+
<Layout
27+
headerFooter={ headerFooter || {} }
28+
seo={ product?.yoast_head_json ?? {} }
29+
uri={ `/product/${ product?.slug ?? '' }` }
30+
>
2731
<SingleProduct product={ product }/>
2832
</Layout>
2933
);

src/components/layout/index.js

+25-1
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,36 @@
1+
/**
2+
* External Dependencies
3+
*/
4+
5+
import Head from 'next/head';
6+
7+
/**
8+
* Internal Dependencies.
9+
*/
110
import { AppProvider } from '../context';
211
import Header from './header';
312
import Footer from './footer';
13+
import Seo from '../seo';
14+
import { replaceBackendWithFrontendUrl, sanitize } from '../../utils/miscellaneous';
415

5-
const Layout = ({children, headerFooter}) => {
16+
17+
const Layout = ({children, headerFooter, seo, uri }) => {
618
const { header, footer } = headerFooter || {};
19+
const yoastSchema = seo?.schema ? replaceBackendWithFrontendUrl( JSON.stringify( seo.schema ) ) : null;
20+
721
return (
822
<AppProvider>
923
<div>
24+
<Seo seo={ seo || {} } uri={ uri || '' }/>
25+
<Head>
26+
{ header?.favicon ? <link rel="shortcut icon" href={ header.favicon }/> : null }
27+
{ yoastSchema ? ( <script
28+
type="application/ld+json"
29+
className="yoast-schema-graph"
30+
key="yoastSchema"
31+
dangerouslySetInnerHTML={ { __html: sanitize( yoastSchema ) } }
32+
/> ) : null }
33+
</Head>
1034
<Header header={header}/>
1135
<main className="container mx-auto py-4 min-h-50vh">
1236
{children}

src/components/seo/index.js

+91
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { NextSeo } from 'next-seo';
2+
import PropTypes from 'prop-types';
3+
4+
/**
5+
* Custom SEO component
6+
*
7+
* Used to seo meta tags for each page
8+
*
9+
* @param {Object} seo Seo.
10+
* @param {string} uri Page URI.
11+
* @see https://www.npmjs.com/package/next-seo
12+
*
13+
* @returns {JSX.Element}
14+
*
15+
*/
16+
const Seo = ( { seo, uri } ) => {
17+
18+
if ( ! Object.keys( seo ).length ) {
19+
return null;
20+
}
21+
22+
const {
23+
title,
24+
description,
25+
og_title,
26+
og_description,
27+
og_image,
28+
og_site_name,
29+
robots,
30+
} = seo || {};
31+
32+
const currentLocation = 'undefined' !== typeof window ? window.location.origin : null;
33+
const opengraphUrl = ( process.env.NEXT_PUBLIC_NEXTJS_SITE_URL ? process.env.NEXT_PUBLIC_NEXTJS_SITE_URL : currentLocation ) + uri;
34+
35+
return (
36+
<NextSeo
37+
title={ title || og_title }
38+
description={ description || og_description }
39+
canonical={ opengraphUrl }
40+
noindex={ 'noindex' === robots?.index }
41+
nofollow={ 'nofollow' === robots?.follow }
42+
robotsProps={{
43+
maxSnippet: parseInt( robots?.['max-snippet']?.replace( 'max-snippet:', '' ) ?? '' ),
44+
maxImagePreview: robots?.['max-image-preview']?.replace( 'max-image-preview:', '' ) ?? '',
45+
maxVideoPreview: parseInt( robots?.['max-video-preview']?.replace( 'max-video-preview:', '' ) ?? '' ),
46+
}}
47+
openGraph={ {
48+
type: 'website',
49+
locale: 'en_US',
50+
url: opengraphUrl,
51+
title: og_title,
52+
description: og_description,
53+
images: [
54+
{
55+
url: og_image.length ? ( og_image[0]?.url ?? '' ) : '',
56+
width: 1280,
57+
height: 720,
58+
},
59+
],
60+
site_name: og_site_name,
61+
} }
62+
twitter={ {
63+
handle: '@Codeytek',
64+
site: '@Codeytek',
65+
cardType: 'summary_large_image',
66+
} }
67+
/>
68+
);
69+
};
70+
71+
Seo.propTypes = {
72+
seo: PropTypes.object,
73+
};
74+
75+
Seo.defaultProps = {
76+
seo: {
77+
title: '',
78+
description: '',
79+
og_title: '',
80+
og_description: '',
81+
og_image: [],
82+
og_site_name: '',
83+
robots: {
84+
follow: '',
85+
index: '',
86+
},
87+
article_modified_time: '',
88+
},
89+
};
90+
91+
export default Seo;

src/utils/miscellaneous.js

+20-1
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,24 @@ import DOMPurify from 'dompurify';
88
* @return {string} Sanitized string
99
*/
1010
export const sanitize = ( content ) => {
11-
return process.browser ? DOMPurify.sanitize( content ) : content;
11+
return 'undefined' !== typeof window ? DOMPurify.sanitize( content ) : content;
1212
};
13+
14+
/**
15+
* Replace backend url with front-end url.
16+
*
17+
* @param {String} data Data.
18+
*
19+
* @return formattedData Formatted data.
20+
*/
21+
export const replaceBackendWithFrontendUrl = ( data ) => {
22+
if ( ! data || 'string' !== typeof data ) {
23+
return '';
24+
}
25+
26+
// First replace all the backend-url with front-end url
27+
let formattedData = data.replaceAll( process.env.NEXT_PUBLIC_WORDPRESS_SITE_URL, process.env.NEXT_PUBLIC_SITE_URL );
28+
29+
// Replace only the upload urls for images back to back to back-end url, since images are hosted in the backend.
30+
return formattedData.replaceAll( `${ process.env.NEXT_PUBLIC_SITE_URL }/wp-content/uploads`, `${ process.env.NEXT_PUBLIC_WORDPRESS_SITE_URL }/wp-content/uploads` );
31+
}

yarn.lock

+8-3
Original file line numberDiff line numberDiff line change
@@ -1568,6 +1568,11 @@
15681568
"resolved" "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz"
15691569
"version" "3.3.4"
15701570

1571+
"next-seo@^5.15.0":
1572+
"integrity" "sha512-LGbcY91yDKGMb7YI+28n3g+RuChUkt6pXNpa8FkfKkEmNiJkeRDEXTnnjVtwT9FmMhG6NH8qwHTelGrlYm9rgg=="
1573+
"resolved" "https://registry.npmjs.org/next-seo/-/next-seo-5.15.0.tgz"
1574+
"version" "5.15.0"
1575+
15711576
"next-stripe@^1.0.0-beta.9":
15721577
"integrity" "sha512-gjYcy81qDikkz3/4EBaCmzNEa4/yccQGUQzSRQD2oW2VwXuuSAflwy9JzgcnGt8/B6amrobDN2uBHlmy/iyybg=="
15731578
"resolved" "https://registry.npmjs.org/next-stripe/-/next-stripe-1.0.0-beta.9.tgz"
@@ -1576,7 +1581,7 @@
15761581
"@babel/runtime" "7.12.5"
15771582
"stripe" "8.132.0"
15781583

1579-
"next@^12.3.0":
1584+
"next@^12.3.0", "next@^8.1.1-canary.54 || >=9.0.0":
15801585
"integrity" "sha512-GpzI6me9V1+XYtfK0Ae9WD0mKqHyzQlGq1xH1rzNIYMASo4Tkl4rTe9jSqtBpXFhOS33KohXs9ZY38Akkhdciw=="
15811586
"resolved" "https://registry.npmjs.org/next/-/next-12.3.0.tgz"
15821587
"version" "12.3.0"
@@ -2321,7 +2326,7 @@
23212326
"iconv-lite" "0.4.24"
23222327
"unpipe" "1.0.0"
23232328

2324-
"react-dom@^17.0.2 || ^18.0.0-0", "react-dom@^18.2.0":
2329+
"react-dom@^17.0.2 || ^18.0.0-0", "react-dom@^18.2.0", "react-dom@>=16.0.0":
23252330
"integrity" "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g=="
23262331
"resolved" "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz"
23272332
"version" "18.2.0"
@@ -2339,7 +2344,7 @@
23392344
"resolved" "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
23402345
"version" "16.13.1"
23412346

2342-
"react@^16.0.0 || ^17.0.0 || ^18.0.0", "react@^16.8.0 || ^17.0.0 || ^18.0.0", "react@^17.0.2 || ^18.0.0-0", "react@^18.2.0", "react@>= 16.8.0 || 17.x.x || ^18.0.0-0":
2347+
"react@^16.0.0 || ^17.0.0 || ^18.0.0", "react@^16.8.0 || ^17.0.0 || ^18.0.0", "react@^17.0.2 || ^18.0.0-0", "react@^18.2.0", "react@>= 16.8.0 || 17.x.x || ^18.0.0-0", "react@>=16.0.0":
23432348
"integrity" "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ=="
23442349
"resolved" "https://registry.npmjs.org/react/-/react-18.2.0.tgz"
23452350
"version" "18.2.0"

0 commit comments

Comments
 (0)