Can you have multiple, additive 'HEAD' elements in NextJS? - next.js

I want to be able to have a 'master' HEAD element in _document.js and for certain pages have an additional HEAD element that adds to what is in the 'master'. From https://nextjs.org/docs/api-reference/next/head it would seem that this is possible.
However, when searching for the answer in Stack Overflow, I found this post, which seems to indicate that this can lead to unexpected results.
If I can't have multiple HEAD elements, do I need to pass data from the individual page through getInitialProps?

You can simple import next/head into any page/component when you need to do something with it
Example:
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
and also have a default Head within _document.js
import Document, { Head, Main, NextScript } from 'next/document';
export default class MyDocument extends Document {
render() {
return (
<html lang="en">
<Head>
<link rel="shortcut icon" href="/favicon.ico" />
</Head>
<body>
<Main />
<NextScript />
</body>
</html>
);
}
}
I am not experiencing any issue so far.
Something to remember though:
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.

Related

Importing a css file for a different page in React

I am having an issue where when I try and import css onto each page, it will appear on all pages instead of just one. I have my CSS importing in _document.js
import { Html, Head, Main, NextScript } from 'next/document'
export default function Document() {
return (
<Html>
<Head>
<link rel="stylesheet" href="/styles/index.css" />
<link rel="stylesheet" href="index.css"/>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.1/css/fontawesome.min.css" integrity="sha512-xX2rYBFJSj86W54Fyv1de80DWBq7zYLn2z0I9bIhQG+rxIF6XVJUpdGnsNHWRa6AvP89vtFupEPDP8eZAtu9qA==" crossOrigin="anonymous" referrerPolicy="no-referrer" />
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
and I want to know how I can import this CSS on a per-page basis, instead of having all css files on all pages. I have tried importing the css inside of each JS file, but it seems to want to go to /pagehere/styles/settings.css instead of /styles/settings.css even when I put import '../styles/settings.css'. Thanks.
Remove the <Link>s and try using import '../../stryles/settings.css' instead.

Any nextjs example where I need to use _document over _app

It seems I can do everything that I need to do in both _app and _document for my application just in _app like
// _app.tsx
import Head from 'next/head';
export default function App({ Component, pageProps }) {
return (
<>
<Head>
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no, user-scalable=no"
/>
<title>Payment</title>
</Head>
<Component {...pageProps} />
</>
);
}
Then when should I use _document necessarily? I found _document pointless because:
I shouldn't place viewport meta tags in _document as nextjs compiler warnings about it. (I thought _document is a good place for markups in head because _document gets called only once on server-side and not on client-side) If I can't place everything of a kind like head markup in one place, I'd like to avoid using that way.
You'll need _document if you want to customize a page's <html> and <body> tags. For example...
Adding the global lang attribute:
<Html lang="en">
Adding custom styles:
<body className="bg-white">
_document is where you put third party links and scripts
import Document, { Html, Head, Main, NextScript } from 'next/document'
class MyDocument extends Document {
render() {
return (
<Html>
<Head>
<link
href="https://fonts.googleapis.com/css2?family=Inter&display=optional"
rel="stylesheet"
/>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}
export default MyDocument
If you are using styled components you need to configure it in _document.jsx file.
https://github.com/massaaki/nextjs-with-styled-component/blob/main/src/pages/_document.tsx

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>
)
}

how to set meta tag dynamically in nextjs

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>

Resources