I use Next 12.
Thanks to Sean's comment here, I was able to progress.
I personally update the tags for SEO purpose according to the consulted page with a component Seo that I crafted.
But to prevent importing it in every pages, I wanted to import it once. It happened that importing this Seo component in pages/_app.js was eventually just duplicating the tags and especially breaking my social media links.
So here is my code.
First the Seo component:
import Head from 'next/head'
import {SEO} from '@/Constants/seo'
const titlePrepend = 'WeekandGO | '
const Seo = ({ seo }) => {
const currentUrl = typeof window !== 'undefined' && window.location.href
const defaultSeo = {...SEO['index']}
let seoWithDefaults = {...defaultSeo}
if (seo) {
seoWithDefaults = {
...seoWithDefaults,
...seo,
}
}
const fullSeo = {
...seoWithDefaults,
metaTitle: seoWithDefaults?.metaTitle ? `${titlePrepend}${seoWithDefaults.metaTitle}` : `${titlePrepend}${defaultSeo.metaTitle}`,
shareImage: seoWithDefaults?.shareImage || defaultSeo.shareImage,
metaDescription: seoWithDefaults?.metaDescription || defaultSeo.metaDescription,
}
return <Head>
<title>{fullSeo.metaTitle}</title>
<meta name="Author" lang="fr" content="Leb Communication"/>
<meta name="Identifier-URL" content="www.weekandgo.com"/>
<meta name="Reply-to" content=""/>
<meta name="revisit-after" content="7"/>
<meta name="Publisher" content="WeekandGO"/>
<meta name="Copyright" content="WeekandGO"/>
<link rel="canonical" href="https://www.weekandgo.com/" />
<meta name="keywords" content={fullSeo.metaKeywords} />
<meta name="description" content={fullSeo.metaDescription} />
<meta name="image" content={fullSeo.shareImage} />
<meta property="og:site_name" content="WeekandGO" />
<meta property="og:url" content={currentUrl || 'https://www.weekandgo.com/'} />
<meta property="og:type" content="website" />
<meta property="og:image" content={fullSeo.shareImage} />
<meta property="og:title" content={fullSeo.metaTitle} />
<meta property="og:description" content={fullSeo.metaDescription} />
<meta name="twitter:title" content={fullSeo.metaTitle} />
<meta name="twitter:description" content={fullSeo.metaDescription} />
<meta name="twitter:image" content={fullSeo.shareImage} />
</Head>
}
export default Seo
I use it in pages/_document.js:
class MyDocument extends Document {
render() {
return (
<Html lang="fr">
<Head>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
<meta name="theme-color" content="#000000" />
<link rel="apple-touch-icon" href="/logo192.png" />
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap&subset=latin-ext" rel="stylesheet" />
<link rel="stylesheet" href="https://use.typekit.net/uah4cdm.css" />
<link rel="manifest" href="/manifest.json" />
<script src="https://maps.googleapis.com/maps/api/js?key=<key>" type="text/javascript"/>
</Head>
<Seo/>
<body className={ !isMobile && 'u-hover-on'}>
<Main />
<NextScript />
</body>
</Html>
)
}
}
Then I import this component in pages/_document.js as so:
import Document, { Html, Head, Main, NextScript } from 'next/document'
import { isMobile } from 'react-device-detect'
import Seo from '@/Shared/Seo'
class MyDocument extends Document {
render() {
return (
<Html lang="fr">
<Head>
<link rel="icon" href="/favicon.ico" />
<meta name="theme-color" content="#000000" />
<link rel="apple-touch-icon" href="/logo192.png" />
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap&subset=latin-ext" rel="stylesheet" />
<link rel="stylesheet" href="https://use.typekit.net/uah4cdm.css" />
<link rel="manifest" href="/manifest.json" />
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyDiB40c-5x-GAEBw3mIJXPTvamFHUV536I" type="text/javascript"/>
</Head>
<Seo/>
<body className={ !isMobile && 'u-hover-on'}>
<Main />
<NextScript />
</body>
</Html>
)
}
}
export default MyDocument
Then, let me spare you some lines to just look at the return of my functional component App in pages/_app.js. There is no import of Seo component, only a ** Head coming from 'next/head' ** is used :
...
return (<SessionProvider session={session}>
<TokensRefresher/>
<Head>
<meta charSet="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1" />
</Head>
<GlobalContextProvider appProps={{ global, categoryAnnexes, categoriesInfos }}>
<GlobalLayout error={error}>
<Component {...rest} />
</GlobalLayout>
</GlobalContextProvider>
</SessionProvider>)
}
...
And finally, here is a page I receive data from server side props and that I want to use to change the meta tags related to seo. Here again I don't show the part of the code that are useless for the matter :
...
const Annonce = ({ ad, ...pageProps }) => {
return <>
<Seo seo={{metaTitle: ad?.slug, metaDescription: ad?.dealerWord, shareImage: ad?.firstImage?.fileUrl}} />
<AnnonceLayout ad={ad} {...pageProps} />
</>
}
...
The result I have in the source code is there is no duplicated meta tags and I can share my links on social medias with no image, description or page tilte overlaps