how to set meta tag dynamically in nextjs - next.js

how to use NextHead in next js and set open graph tag. I am passing props from the detail page but It is not appearing in the source.
<NextHead>
<title>{title}</title>
<meta property="og:type" content="website"/>
<meta name="description" content={description}/>
<meta property="og:title" content={title}/>
<meta name="description" content={description}/>
<meta name="keywords" content={keyword}/>
<meta property="og:url" content={url}/>
<meta property="og:description" content={description}/>
<meta property="og:image" content={image}/>
</NextHead>

So here's what I found. In the newer version of Next.js (without getInitialProps), whenever I created the meta tag in a page or component it would show up in the head when I inspected the page, but it wouldn't show up in the head when I opened the 'page source'. While debugging the problem, I tried to pass my dynamic meta tags into _app.js via pageProps and it worked! I'm still not sure why this happens and whether there is a better solution. But here's what I did:
_app.js:
function App({ Component, {metaTags, ...rest} }) {
return (
<>
<Head>
{metaTags &&
Object.entries(metaTags).map((entry) => (
<meta property={entry[0]} content={entry[1]} />
))}
</Head>
<Component {...rest} />
</>
)
}
Here's what my getServerSideProps object looked like. It used the pre-fetched event data to create the metaTags and passed it in props:
export async function getServerSideProps({ params }) {
const { id: slug } = params;
const {
data: { event },
} = await getEventLandingDetailsApi(slug);
const metaTags = {
"og:title": `${event.title} - ${event.edition}, ${event.country} Ticket Price, Registration, Dates & Reviews`,
"og:description": event.description.split(0, 150),
"og:image": event.logo.url,
"og:url": `https://someurl.com/events/${event.slug}`,
};
return {
props: {
event,
metaTags
}
}
}

You can see from the example here, next/head is imported and will add specific meta tag to the specific page.
import Head from 'next/head'
function IndexPage() {
return (
<div>
<Head>
<title>My page title</title>
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
</Head>
<p>Hello world!</p>
</div>
)
}
export default IndexPage
If this is not working, please provide the error message from your dev console. There should be some error that cause this approach not working.

One of the issues is that the Next.js Head component requires all meta tags to have a name attribute. I don't see this documented anywhere and I believe this is why for example this
<meta property="og:url" content={url}/>
from the original question did not end up in the DOM. It took me quite some time to understand this gotcha, so I hope this helps someone.

In your config.js file in the root folder, export the data you want to include in your meta tags. E.g.
export const MY_SEO = {
title: 'MyTitle',
description: 'My description',
openGraph: {
type: 'website',
url: 'My URL'
title: 'MyTitle',
description: 'My description',
image: '...jpg',
}
};
Inside a Meta.js file in your components folder, you could have:
import Head from 'next/head';
import { MY_SEO } from '../config';
const Meta = () => (
<Head>
<title key="title">{MY_SEO.title}</title>
<meta
key="description"
name="description"
content={MY_SEO.description}
/>
<meta
key="og:type"
name="og:type"
content={MY_SEO.openGraph.type}
/>
<meta
key="og:title"
name="og:title"
content={MY_SEO.openGraph.title}
/>
<meta
key="og:description"
name="og:description"
content={MY_SEO.openGraph.description}
/>
<meta
key="og:url"
name="og:url"
content={MY_SEO.openGraph.url}
/>
<meta
key="og:image"
name="og:image"
content={MY_SEO.openGraph.image}
/>
</Head>
);
export default Meta;
In your pages/_app.js, add
import Meta from '../components/Meta';
to your import statements, and simply add the <Meta /> component.

Why need to have metadata tag every page? It should be set at your root page. Try this plugin, https://github.com/garmeeh/next-seo

Use the <Head> component exposed by next/head.
import Head from 'next/head';
const IndexPage = ({ data }) => (
<div>
<Head>
<title>My page title</title>
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
{data?.pageType && <meta name="page-type" content={data?.pageType} />}
{data?.pageHype && <meta name="page-hype" content={data?.pageHype} />}
</Head>
<p>Hello world!</p>
</div>
);
export default IndexPage;
There are some gotchas, though. From their documentation:
The contents of head get cleared upon unmounting the component, so
make sure each page completely defines what it needs in head, without
making assumptions about what other pages added.
The trickiest, which will have you loose your hair is this one:
title, meta or any other elements (e.g. script) need to be contained
as direct children of the Head element, or wrapped into maximum one
level of <React.Fragment> or arrays—otherwise the tags won't be
correctly picked up on client-side navigations.
Creating a component that contained the conditionally rendered meta tags was already too much, because that made the return too deep for Next. Make sure your meta tags are directly under <Head> or, as the documentation says, wrapped into maximum one <React.Fragment> or condition, as illustrated in my code snippet.

I'm writing this as a reference for everyone, If your running next.js version from 9.5.2 just add the following <Head /> as an example create a document inside your pages dir i.e _document.js that will overwrite a default document provided by next.js like so
_document.js
import Document, { Html, Head, Main, NextScript } from 'next/document';
class MyDocument extends Document {
render() {
return (
<Html lang="en">
<Head /> // do this
<meta charSet="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link
rel="stylesheet"
href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" />
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
export default MyDocument;

Just need to change from property to name, it will work.
<NextHead>
<title>{title}</title>
<meta name="og:type" content="website"/>
<meta name="description" content={description}/>
<meta name="og:title" content={title}/>
<meta name="description" content={description}/>
<meta name="keywords" content={keyword}/>
<meta name="og:url" content={url}/>
<meta name="og:description" content={description}/>
<meta name="og:image" content={image}/>
</NextHead>

Related

How to add a favicon to a nextjs app structure? - possible hydration issue

I'm trying to figure out how to add a favicon file to a next.js app (with react 18).
I have made a _document page that has a head tag as follows:
import * as React from "react"
// import {createRoot} from 'react-dom/client'
import { ColorModeScript } from "#chakra-ui/react"
import Document, { Head, Html, Main, NextScript } from "next/document"
import Favicon from "../components/Favicon"
export default class AppDocument extends Document {
static getInitialProps(ctx: any) {
return Document.getInitialProps(ctx)
}
render() {
return (
<Html lang="en">
<Head>
<meta name="theme-color" key="theme-color" content="#000000" />
<meta name="description" content="name" key="description" />
<meta property="og:title" content="title goes here" key="title" />
<meta property="og:description" content="description goes here" key="og:description" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" />
<Favicon />
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}
I then made a component called Favicon with:
import React from "react";
const Favicon = (): JSX.Element => {
return (
<React.Fragment>
<link rel="apple-touch-icon" sizes="76x76" href="/apple-touch-icon.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
<link rel="manifest" href="/site.webmanifest" />
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5" />
<meta name="msapplication-TileColor" content="#da532c" />
<meta name="theme-color" content="#ffffff" />
</React.Fragment>
)
}
export default Favicon;
I then made a root/packages/src/public folder (this folder is at the same level and place as the pages folder that has the _document.tsx file) and saved each of the assets to it.
I don't get an error, but the favicon does not populate in the browser tab.
How can I add a favicon in nextjs?
I also tried removing the Favicon component and moving the meta tags directly to app.tsx. It still doesnt render the favicon.
I can see from the console errors that the files are not found. They are all saved in the project at public/[file name]. Public is a folder at the same level as the pages directory.
I have working a working favicon in a Next.js 12 + React 18 app by adding the icon link directly to the <Head> and the images in public/[file name].
render() {
return (
<Html lang="en">
<Head>
<link rel="shortcut icon" href="/favicon.ico" />
I assume that your React component is not working because the browser is trying to load the favicon before the page has been hydrated with any Javascript that can generate your React component favicon.
I solved this problem. You need to add the image to the public directory. Next engine will detect this favicon automatically.
but if you need to add special image or etc, you must add image file into public/static folder. and your url should be start with static/*. like this:
<React.Fragment>
<link rel="icon" sizes="76x76" href="static/apple-touch-icon.png" />
</React.Fragment>
And finally, to display the icon in the browser tab, you must perform a hard refresh Ctrl+F5;
You should just add/replace the icon file with the name favicon.ico in the root of the public folder with your icon. If you want to do SEO consider using "next-seo" package
What I do, I create a component say Meta.tsx and use it inside my Pages
import { NextSeo } from "next-seo"
import type { OpenGraph } from "next-seo/lib/types"
import { useRouter } from "next/router"
import type { MetaProps } from "../../types/content"
export const Meta = ({
type = "website",
siteName = "My-site-name",
data,
}: MetaProps): React.ReactElement => {
const router = useRouter()
// TODO Make generator based on different data
const seoData = data
const locale = "en_EN"
const baseURL = process.env.BASE_URL || window.location.origin
const currentURL = baseURL + router.asPath
const seo = {
title: seoData.title,
titleTemplate: `%s - ${siteName}`,
defaultTitle: siteName,
canonical: currentURL,
}
const openGraph: OpenGraph = {
title: seoData.title,
type,
locale,
url: currentURL,
site_name: siteName,
images: seoData.images,
}
const metaLinks = [
{
rel: "icon",
type: "image/svg+xml",
href: "/favicon.svg",
},
{
rel: "apple-touch-icon",
href: "/touch-icon-ipad.jpg",
sizes: "180x180",
},
{
rel: "mask-icon",
type: "image/svg+xml",
color: "#0d2e41",
href: "/favicon.svg",
},
{
rel: "icon",
href: "/favicon.ico",
},
]
return (
<NextSeo
{...seo}
openGraph={openGraph}
additionalLinkTags={metaLinks}
noindex={data.requireAuth || data.noIndex}
/>
)
}
I think you are just missing the correct rel attribute. This works in my project:
import {Html, Head, Main, NextScript} from 'next/document';
export default function Document() {
return (
<Html lang="en">
<Head>
<link rel="shortcut icon" href="/site/images/favicon.ico" />
...
where /site from /site/images/favicon.ico is in the public folder.
import Head from "next/head";
<Head>
// If favicon path is public/images/
<link rel="icon" type="image/x-icon" href="/images/favicon.ico" />
</Head>
Then you can render this inside Header component or Lay out component. this
<link rel="icon" type="image/x-icon" href="/images/favicon.ico" /> should work also in _document page if you pass correct `href
The easiest way is to just put the <Head> in your layout component.
import Head from "next/head"
const Layout = ({ children, home }: Props) => {
return (
<>
<div className={styles.container}>
<Head>
<link rel="icon" type="image/svg" href="/icons/YOURICON.svg" />
....rest of your <meta> tags for SEO ....
</Head>
....rest of your html/jsx like page header/main-stage/footer/etc..
)}
Make sure that your icon is in a correct format, put in a folder the image named icon.png and use imagemagick to convert it to .icon format
brew install imagemagick
convert icon.png -scale 16 tmp/16.png
convert icon.png -scale 32 tmp/32.png
convert icon.png -scale 48 tmp/48.png
convert icon.png -scale 128 tmp/128.png
convert icon.png -scale 256 tmp/256.png
convert tmp/16.png tmp/32.png tmp/48.png tmp/128.png tmp/256.png icon.ico
Then i use to generate a component for the initial head content HeadContent.js
const HeadContent = () => (
<>
<link
rel="shortcut icon"
sizes="16x16 24x24 32x32 48x48 64x64"
href="/favicon.ico"
/>
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<meta content="yes" name="apple-mobile-web-app-capable" />
<meta
name="apple-mobile-web-app-status-bar-style"
content="black-translucent"
/>
</>
);
export default HeadContent;
Now in _document.js you can use the HeadContent.js:
import Document, { Html, Head, Main, NextScript } from "next/document";
import HeadContent from "#components/HeadContent";
export default class extends Document {
render() {
return (
<Html lang="es-co">
<Head>
<meta charSet="UTF-8" />
<HeadContent />
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
inside pages/_app.tsx
import Head from "next/head";
<Head>
<link rel="icon" href="/favicon.ico" />
</Head>

SEO meta tags are showing in page source but cannot preview them

In my Next.js app I'm trying to insert og meta tags with dynamic content on a product page. So the content of the meta tags will change based on the product data fetched from server.
I am fetching product data using getServerSideProps and passing product data to page component as props.
export const getServerSideProps = wrapper.getServerSideProps(
store => async (context) => {
const response = await fetch(url, {
method: 'GET',
});
const data = await response.json();
return {
props: {
host: context.req.headers.host,
product: data.product
}
}
}
)
First Approach
I tried to insert meta tags directly on my product page component within <Head> component. Here meta tags even with static conetnt are not showing in page source.
const Product = ({product}) => {
return (
product ?
<>
<Head>
<title>{product.title}</title>
<meta name="description"
content={product.description}/>
<meta property="og:title" content={product.title}/>
<meta property="og:type" content="video.movie"/>
<meta property="og:url" content={`${props.host}/products/${product.slug}`}/>
<meta property="og:description" content={product.description}/>
<meta property="og:image" content={product.thumbnail}/>
</Head>
<Course/>
</> : null
);
};
Second Approach
return (
<Html lang="en">
<Head>
{/*<meta property="og:image" content="https://static.onlinevideobooks.com/abed1e5658b3ad23c38c22646968e4f2/files/media/images/2022/04/5b0645b9-ab03-4233-b5f3-86b092d4062b/conversions/cad47d2beb9143eab3d65ea4a75b0b0e-1280x720.webp" />*/}
{/*<title>your keyword rich title of the website and/or webpage</title>*/}
<meta name="description"
content="description of your website/webpage, make sure you use keywords!"/>
<meta property="og:title" content="short title of your website/webpage"/>
<meta property="og:type" content="video.movie"/>
<meta property="og:url" content="https://example.com/"/>
<meta property="og:description" content="description of your website/webpage"/>
<meta property="og:image"
content="https://example.com/image"/>
</Head>
</Html>
);
I tried inserting meta tags within <Head> in _document.js file. Here I am passing only static conetnt as I don't have dynamic data in _document.js. This time meta tags are showing up in page source and I can also preview them on facebook.
Third Approach
Then I try to insert those tag in _app.js file as I receive pageProps in this component.
Unfortunately when I pass dynamic content in meta tags like first approach, they do not show up in page source but they do when I pass static conetnt similar to second approach.
full _app.js code gist
UPDATE
As regard to my third approach, I checked once again and surprisingly I can see all meta tags in page source when inserted either with static or dynamic content in _app.js. I can preview the url when content is static but when content is dynamic I can not preview the url using either Facebook debug or Open graph
My Next.js version is 12.2.0
It's possible that the code you have in your _app.js is blocking the meta tags from being rendered. The "View page source" in browsers will not wait for client code to finish rendering. You can verify this and click "View page source" from your browser. Do you see any of the HTML you are expecting, for example do you see your meta tags and product html?
I expect that you probably don't see anything except some static HTML tags. One thing you could try is moving your use of hooks and rendering logic down into its own MainLayout component.
You can then try your first approach where you do something like this:
const Product = ({product}) => {
return (
product ?
<>
<Head>
<title>{product.title}</title>
<meta name="description"
content={product.description}/>
<meta property="og:title" content={product.title}/>
<meta property="og:type" content="video.movie"/>
<meta property="og:url" content={`${props.host}/products/${product.slug}`}/>
<meta property="og:description" content={product.description}/>
<meta property="og:image" content={product.thumbnail}/>
</Head>
<MainLayout>
<Course/>
<MainLayout>
</> : null
);
};
Where MainLayout contains all the logic you have in your _app.js. This should keep your actual _app.js free of any client side code that is blocking the meta tags from rendering.
Basically we want to utitlize Next.js static optimization and have it pre-render the meta tags for your page so that the browser and web crawlers get the data without having to wait for any client side rendering.
Hopefully this helps someone, but my issue was the placement of the <Head> tags within _app.js. For some reason, when it was nested under <Auth0ProviderWithHistory> the meta tags would not render, moving it outside of this gave me victory.
Broken
<Auth0ProviderWithHistory>
<Head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
{openGraphData.map((og) => (
<meta {...og} />
))}
<title>{pageProps.title}</title>
</Head>
<CssBaseline>
<div className="page-layout">
<NavBar />
<Component {...pageProps} />
<Footer />
</div>
</CssBaseline>
</Auth0ProviderWithHistory>
Fixed
<span>
<Head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
{openGraphData.map((og) => (
<meta {...og} />
))}
<title>{pageProps.title}</title>
</Head>
<Auth0ProviderWithHistory>
<CssBaseline>
<div className="page-layout">
<NavBar />
<Component {...pageProps} />
<Footer />
</div>
</CssBaseline>
</Auth0ProviderWithHistory>
</span>
Not sure what's going under the hood, but removing the Head tag resolves this for me.
I just put my meta tags without nesting them inside Head, and it worked.

NextJs meta tag in next/head not available to Facebook and metatags.io

I create a component SeoHeader to handle meta tag creation on NextJs project
import React from 'react';
import Head from 'next/head';
import { useRouter } from 'next/dist/client/router';
const SeoHeader = ({ title, description, image }) => {
const router = useRouter();
return (
<Head>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta charSet="utf-8" />
<meta name="description" content={description} key="description" />
<meta property="og:type" content="website" />
<meta property="og:title" content={title} key="og:title" />
<meta property="og:description" content={description} key="og:description" />
<meta property="og:image" content={image} key="og:image" />
<meta property="og:url" content={`https://www.energici.fr${router?.asPath}`} key="og:url" />
<meta name="twitter:card" content={description} key="twitter:card" />
<meta property="og:site_name" content="Energici, l'info bien être" key="og:site_name" />
<meta
property="twitter:url"
content={`https://www.energici.fr${router?.asPath}`}
key="twitter:url"
/>
<meta
name="twitter:image:alt"
content="Energici la plateforme du bien être"
key="twitter:image:alt"
/>
<meta property="twitter:domain" content="energici.fr" />
<title>{title}</title>
</Head>
);
};
export default SeoHeader;
If I import this component on _app.js everything is going well. I can see the meta tags in html dom and if I share the link preview card is ok.
But if I share another page for example this one : https://www.energici.fr/decouvrons-la-kinesiologie
I can see the correct meta tag in html dom but if I share the link in facebook for example or if I test it here https://metatags.io/ I cannot se anything, it's like there is no meta tag.
Any idea ?
Neither the crawler for Facebook, nor the crawler for metatags.io can execute JavaScript. Any HTML tags that are inserted by client side JavaScript won't be available to them.
Googlebot can execute JavaScript, so your meta data may work for Google. The caveat is that Googlebot has to render your pages, so crawling and indexing can take weeks longer than it would otherwise.
You can solve these problems by prerendering your site. After implementing that, crawlers will see HTML with your tags and not have to run JavaScript.

Head component from next/head renders into <body> instead of <head>

I am setting up a project in nextjs. I would like to add some global meta tags and title to the project. I used the Head component from next/head in _app.tsx but they are being rendered in the body instead of the head both on localhost and when deployed using vercel. I have used next before and can't remember running into this issue.
Here is my _app.tsx
import { AppProps } from 'next/app'
import Head from 'next/head'
import Footer from '../components/footer'
import MobileNavBar from '../components/mobileNavBar'
import NavBar from '../components/navBar'
import '../styles.scss'
const App = ({ Component, pageProps }: AppProps) => {
return (
<>
<Head>
<html lang="en" />
<title> Erika's Dog Training </title>
<meta
content="width=device-width, initial-scale=1, maximum-scale=5, shrink-to-fit=no"
name="viewport"
></meta>
<script type="text/javascript" src="https://assets.calendly.com/assets/external/widget.js"></script>
</Head>
<MobileNavBar />
<NavBar />
<Component {...pageProps} />
<Footer />
</>
)
}
export default App;
So the meta tag and title here are showing up on the page, but at the top of the <body> tag instead of in <head>
I had the same problem with next: "11.1.2". Removing <html lang="en" /> fixed the problem. I added the lang value back using: (https://melvingeorge.me/blog/set-html-lang-attribute-in-nextjs)
/* next.config.js */
module.exports = {
i18n: {
locales: ["en"],
defaultLocale: "en",
},
};
Consider the structure of this example from the nextjs documentation.
import Document, { Html, Head, Main, NextScript } from 'next/document'
class MyDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx)
return { ...initialProps }
}
render() {
return (
<Html>
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}
export default MyDocument
The main differences that I see, is that in the documentation they place both head and body as siblings within <Html> tags. I suspect that is your problem. I would also remove the empty <> and </> tags.
You have the line: <html lang="en" /> Inside the Head tag. This is going to make it challenging for next to generate the correct html as the html tag is generally above both Head and body tags.
Place <html lang="XX"/> as the last child of <Head>.
Example:
<Head>
<title> Erika's Dog Training</title>
<meta content="width=device-width, initial-scale=1, maximum-scale=5, shrink-to-fit=no" name="viewport" />
<html lang="en" />
</Head>

Set default Head for all pages in NextJS?

Can you set a default value for the Head component in NextJS that other pages will extend from?
In my case I need to load a font on every page:
<Head>
<link
href="path-to-font"
rel="stylesheet"
/>
</Head>
I could do this with a custom document file, but a default value for Head seems simpler.
https://nextjs.org/docs/advanced-features/custom-document
If the Head doesn't require change often, you can take the custom document approach. However if you want things to be dynamic like different title for different pages I would suggest keeping the static layout in custome-document and the dynamic layout separate in another component like
in _document.js
// this will define the default layout for the page
render() {
return (
<Html lang="en">
<Head>
<meta httpEquiv="Content-Type" content="text/html; charset=utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0"
/>
// links for static assets
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
If you want things to be dynamic like the title, create a new Layout component that wraps all the components is a specific page.
const Layout = ({children, title="default title", description="default-description"}) => {
return (
<>
<Head>
<meta name="description" content={description} />
<title>{title}</title>
</Head>
// you can header component here
<main>
{children}
</main>
// you can add your footer component here
</>
);
}
Now you just need to wrap you page with the Layout for things to be dynamic.
const MyPage = (props) => {
return (
<Layout title="the-dynamic-title" description="the-dynamic-description">
// all other components for your page goes here
</Layout>
)
}

Resources