next/head <meta> tags for pages overriding default _app.js <meta> tags are rendered in the browser but not visible to facebook debugger.
Verify canary release
- [X] I verified that the issue exists in Next.js canary release
Provide environment information
sorry this is not working.
i am running
nextjs 12 node 16.12.1 yarn 1.22.5
What browser are you using? (if relevant)
chrome
How are you deploying your application? (if relevant)
VPS
Describe the Bug
Summary: I am trying to create fb and twitter metadata for my whole site by defining a <Head> with metadata in _app.js with overrides for specific components using the identical set of tags with some local overrides.
Implementation: I have a two page site with _app.js, one.js and two.js files. I have added a <Head> section into _app.js file and a <Head> section in one.js with some overrides. I have used the keys to ensure that duplicates are not made and when i load the pages in the browser I can see that this works correctly and component specific overrides are presented.
The relevant metadata is as follows and is contained in both _app.js and one.js (but not two.js which I expect to use _app.js metadata). Use of variables enables me to use an identical metadata and set component specific overrides.
<Head>
... other stuff here
<meta property="og:title" content={title} key="og-title"/>
<meta property="og:description" content={description} key="og-desc"/>
<meta property="og:url" content={router.pathname} key="og-url"/>
<meta property="og:image" content={image} key="og-image" />
<meta property="og:site_name" content="mysitename" key="og-site" />
<meta name="twitter:title" content={title} key="tw-title"/>
<meta name="twitter:description" content={description} key="tw-desc"/>
<meta name="twitter:image" content={image} key="tw-image"/>
<meta name="twitter:card" content="summary_large_image" key="tw-card"/>
</Head>
Result:
Summary: things work as expected in the browser but facebook debugger is not picking up the component specific changes.
In the browser I can see that the correct metadata is loaded for one.js (presenting the component specific overrides) and two.js (presenting the metadata from _app.js). However the facebook debugger is only picking up metadata from _app.js not from one.js - so all pages have the same metadata.
Expected Behavior
Both the facebook debugger and the client browser to load the same metadata. Facebook debugger is somehow not collecting the page specific metadata overrides.
To Reproduce
I have a live site running here:
https://next18.syntapse.co.uk/two doesnt define its own metadata so loads default tags from _app.js using keys to avoid duplication
https//next18.syntapse.co.uk/one loads overrided tags using keys to avoid duplication
This works as expected in the browser. Load this into a browser and note the differences in metadata. Note the metadata changes from the default to the override.
Load this into the facebook debugger and note that /one loads the same metadata as /two and also / url.
I have seen something similar to this with React Helmet. The difference to React Helmet issue is that the facebook debugger doesnt pick up any metadata from the page at all.
Could this be happening because _app.js metadata is being rendered on the server and page metadata is being rendered on the client? If so, could a potential fix therefore be to render all metadata on the server?
Is the data present on the html of the page (View Page Source)? If not, then opengraph tags won't work.
You most likely call that tag on a page component, which is wrapped in several few others in the _app. If any of them use a conditional render relying on the client-side logic, then they will always evaluate to false on the server and thus the entire page renders client-side only.
I'm also running into this - I have some pre-rendered pages (getStaticPaths & getStaticProps) that are meant to inject some SEO data into the head using next/head. It appears that all of the page code is being ran client side instead of being fully pre-rendered. Using view source in Chrome I can see the head tags show the defaults from _app.js but inspect element shows the tags are being properly swapped out, indicating client side manipulation of the head tag.
Either this is a bug or it's very very unclear in the docs why this occurs.
The whole purpose of this for us is to get some SEO, so I would love for a solution to be found.
I did some experimenting, removing the next/head component from _app.js results in there being no head, title, meta, etc tags when the source is viewed on Chrome.
Another thing - even pages with no getStaticProps or getStaticPaths don't work.
I can see on Google when I search for <my company> login the login page comes up as the first link but the title is not replaced - it just shows the default title from _app.js
Again, without seeing the structure of _app it's hard to tell the source of the problem.
Again, without seeing the structure of
_appit's hard to tell the source of the problem.
Ended up doing some deeper dives (trying to reproduce in CodeSandbox) and I think the issue may be in other ways I am using _app.js. I am loading the Google Maps API script which includes a brief loading script, preventing the child component from loading on first render (the page).
Will do some further testing but was unable to reproduce taking a simplified version of my exact setup on CodeSandbox - so I think the culprit is an implementation error.
Thanks for responding & prompting me to look into this some more!
@PorterK when you say not able to repro on codesandbox do you suggest it is working ok with codesandbox? i.e. share works in facebook debugger?
@GabenGar I dont have any conditions for rendering the overriden component. I set up the simplest possible live server to test this out and the code and URLS I am testing are below.
Quick summary I have defined default metatags on _app.tsx. /one ovverides metatags (no conditonal rendering) /two uses default metatags.
result: works as expected in browser but not for facebook debugger.
_app.tsx
function MyApp({ Component, pageProps }: AppProps) {
let router = useRouter();
let title = "Default metatags ";
let description = "Metadata defined in Next js custom _app";
return <>
<Head>
<link rel="shortcut icon" href={`${hostURL}/files/images/icons/syntapse_favicon.png`}/>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="title" content="Syntapse Software" />
<link rel="shortlink" href={`${hostURL}`} />
<link rel="canonical" href={`${hostURL}`} />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta property="og:title" content={title} key="og-title"/>
<meta property="og:description" content={description} key="og-desc"/>
<meta property="og:url" content={`${hostURL}${router.pathname}`} key="og-url" />
<meta property="og:image" content={image} key="og-image" />
<meta property="og:site_name" content="Syntapse Software" key="og-site" />
<meta name="twitter:title" content={title} key="tw-title"/>
<meta name="twitter:description" content={description} key="tw-desc"/>
<meta name="twitter:image" content={image} key="tw-image"/>
<meta name="twitter:card" content="summary_large_image" key="tw-card"/>
</Head>
<Component {...pageProps} />
</>
}
code for URL https://next18.syntapse.co.uk/one
import { useRouter } from "next/router"
import Head from "next/head"
let hostURL = "https://next18.syntapse.co.uk"
export default function Page() {
let router = useRouter();
let title = "Overriden metatags ";
let description = "Metadata defined in Next js page 'one'";
let image = `${hostURL}/images/contact_slide.jpg`;
return <>
<Head>
<meta property="og:title" content={title} key="og-title" />
<meta property="og:description" content={description} key="og-desc" />
<meta property="og:url" content={`${hostURL}${router.pathname}`} key="og-url" />
<meta property="og:image" content={image} key="og-image" />
<meta property="og:site_name" content="Syntapse Software" key="og-site" />
<meta name="twitter:title" content={title} key="tw-title" />
<meta name="twitter:description" content={description} key="tw-desc" />
<meta name="twitter:image" content={image} key="tw-image" />
<meta name="twitter:card" content="summary_large_image" key="tw-card" />
</Head>
<h1>Page One</h1>
</>
}
code for URL https://next18.syntapse.co.uk/two (uses the default metatags)
export default function Page() {
return <>
<h1>Page Two</h1>
</>
}
There are no conditionals in here and the metatags render correctly in the browser but are not recognised by facebooks debugger. Are the page actually being SSR'd, or are they rendered by the browser after initial page load?
@PorterK when you say not able to repro on codesandbox do you suggest it is working ok with codesandbox? i.e. share works in facebook debugger?
I didn't check Facebook Debugger specifically, but looking at view source and inspect element show the same meta tags on codesandbox. The only way I could see it being messed up is if Facebook is doing some extra processing somewhere (definitely possible, haven't looked at it)
view source is the response the browser gets back from the server - if you look at a normal SPA it will not include any HTML other than the root (for example: view-source:https://www.facebook.com/). SSR would return atleast partial HTML (for example: view-source:https://github.com/)
That's usually a pretty good way to tell if the page you are on was rendered by the client or server on the first initial load. I think subsequent link clicks will always occur in the SPA (not 100% clear on this)
I too have checked the page source of the links posted above and only the domain root contains the metatags they are stripped from the pages.
Using the links I posted above neither /one nor /two contain the metatags though they show up in debugger/inspector. Not sure how that is possible.
Question for maintainer: am I expecting something outside of scope of Next JS design i.e. individual pages can provide their own metadata or is there are issue with Next JS given the simple example code I have posted above? Is it reproducable?
To check without a server simply run as the Facebook tool does, using curl or disabling the browser's javascript. For some reason doesn't inject the declared meta during pre-rendering, it does later with JS. So when you check finds that this supposedly works, but when checking it with the tools the changes are not found.
There are no conditionals in here and the metatags render correctly in the browser but are not recognised by facebooks debugger.
What do you mean "render correctly in the browser"? Both of these pages are missing all head tags in the initial html. so they clearly do not render correctly.
Thanks for your continued attention I am most grateful.
Please excuse my terminology here I am still trying to work out what is going wrong. By render correctly in browser: I was initially checking the page as seen by chrome developer tools which (somehow) shows all of the correct headers even though they do not appear in the page source.
If I have supplied all the code I'm using to render the pages how do I inject those headers into the page so I can see them when I "view source? I think that will surely fix the facebook issue.
Also as stated above: "For some reason doesn't inject the declared meta during pre-rendering, it does later with JS." If thats true does this imply that Next JS is not rendering page metadata on the server?
So we dont get too lost in specifics: does Next JS support SSR injection of metatags at a page level to enable a default __app.ts defined set of metatags to be overriden by individual pages similar to the scheme i have used above? Is that meant to work?
By render correctly in browser: I was initially checking the page as seen by chrome developer tools which (somehow) shows all of the correct headers even though they do not appear in the page source.
Dev tools shows you the state of DOM which is a parsed html with all eligible scripts in it activated. If you want to test things like opengraph tags and server-side render in general you have to turn off client javascript in your browser settings. AFAIK there is no way to do this on per-page/site basis without extensions.
If thats true does this imply that Next JS is not rendering page metadata on the server?
No, but you have to be very careful how you write the code for server-rendered pages. Specifically a lot of react hooks do not work server-side, outside of initial state set by React.useState(). Chances are useRouter() is a client-side hook and, due it being a part of nextjs, it is treated "smartly" by the framework. Try to get pathname from getServerSideProps, pass it as props to the page component and see if it fixes the issue.
Possible Solution.
I have spent days looking at this now and I am not convinced that the mechanism for pages overriding _app.tsx
metadata by inclusion of a <Head>in individual pages works for facebook sharing . While the overrides show in the browser debugger they are not picked up by facebook debugger.To workaround this limitation and solve this particular issue I have been able to expose metadata to facebook debugger by having a single <Head> block in _app.tyx and pass page specific metata as props in getServerSideProps. Guessing would also work for getStaticProps.
page.tsx
export async function getServerSideProps(context) {
return {
props: {
title: "Page one! ",
description: "Page One metadata",
image: `${hostURL}/images/page_one_image.jpg`
}
}
}
export default function Page() {
return <div>
<h1>Page One</h1>
</div>
}
_app.tsx
function MyApp({ Component, pageProps }: AppProps) {
let title = pageProps.title ? pageProps.title : "Default metatags ";
let description = pageProps.description ? pageProps.description : "Default description";
let image = pageProps.image ? pageProps.image : `${hostURL}/image/default_image.jpg`;
return <>
<Head>
<meta/>
<other tags/>
</Head>
</>
After messing with this for hours, scouring Google to no avail, the above solution worked for me. Thanks @laurencefass I'm doing something like this:
// Some Next JS Page
export const getStaticProps = async () => {
return {
props: {
openGraphData: [
{
property: "og:image",
content:
"https://glievsbwngosqvrxtupy.supabase.co/storage/v1/object/public/event-banners/Jul%208%20Darkest%20Hour%20LONG.jpeg?t=2022-06-28T21%3A47%3A43.910Z",
key: "ogimage",
},
{
property: "og:image:width",
content: "400",
key: "ogimagewidth",
},
{
property: "og:image:height",
content: "300",
key: "ogimageheight",
},
{
property: "og:url",
content: `http://foobar.com/events`,
key: "ogurl",
},
{
property: "og:image:secure_url",
content:
"https://glievsbwngosqvrxtupy.supabase.co/storage/v1/object/public/event-banners/Jul%208%20Darkest%20Hour%20LONG.jpeg?t=2022-06-28T21%3A47%3A43.910Z",
key: "ogimagesecureurl",
},
{
property: "og:title",
content: "Hey hey",
key: "ogtitle",
},
{
property: "og:description",
content: "Ima description",
key: "ogdesc",
},
{
property: "og:type",
content: "website",
key: "website",
},
],
},
};
};
Then in _app.tsx:
function MyApp({ Component, pageProps }: AppProps) {
console.log("pageProps", pageProps);
const { openGraphData = [] } = pageProps;
return (
<>
<Head>
{openGraphData.map((og) => (
<meta {...og} />
))}
</Head>
<Component {...pageProps} />
</>
);
}
export default MyApp;
There's a line in the docs here: https://nextjs.org/docs/api-reference/next/head about using the key prop to properly de-duplicate meta tags. Hope it helps!
The example is oversimplified and th keys dont seem to be working as expected in cases stated here.
After messing with this for hours, scouring Google to no avail, the above solution worked for me. Thanks @laurencefass I'm doing something like this:
// Some Next JS Page export const getStaticProps = async () => { return { props: { openGraphData: [ { property: "og:image", content: "https://glievsbwngosqvrxtupy.supabase.co/storage/v1/object/public/event-banners/Jul%208%20Darkest%20Hour%20LONG.jpeg?t=2022-06-28T21%3A47%3A43.910Z", key: "ogimage", }, { property: "og:image:width", content: "400", key: "ogimagewidth", }, { property: "og:image:height", content: "300", key: "ogimageheight", }, { property: "og:url", content: `http://foobar.com/events`, key: "ogurl", }, { property: "og:image:secure_url", content: "https://glievsbwngosqvrxtupy.supabase.co/storage/v1/object/public/event-banners/Jul%208%20Darkest%20Hour%20LONG.jpeg?t=2022-06-28T21%3A47%3A43.910Z", key: "ogimagesecureurl", }, { property: "og:title", content: "Hey hey", key: "ogtitle", }, { property: "og:description", content: "Ima description", key: "ogdesc", }, { property: "og:type", content: "website", key: "website", }, ], }, }; };Then in
_app.tsx:function MyApp({ Component, pageProps }: AppProps) { console.log("pageProps", pageProps); const { openGraphData = [] } = pageProps; return ( <> <Head> {openGraphData.map((og) => ( <meta {...og} /> ))} </Head> <Component {...pageProps} /> </> ); } export default MyApp;
Hey @adamjarling, so did this fix the issue where the meta of the child pages get overidden by the _app.js file? Im currently facing this issue and can't get the og tags on the child pages to render on facebook debugger as well as linkedin
@lakindu2002 Just use next-seo. It has all features you need including separation between default and component SEO tags, which are keyed under the hood.
yeah i am using next seo. but the _app.js overiddes all child page next-seo tags
@GabenGar
@lakindu2002 did you report that in the next-seo issue queue?
@laurencefass i dont think its an issue with next-seo
it does its thing. once you open the browser console, you can see the og tags. only issue is when used in linkedin and facebook. they fetch the tags in _app.js
After messing with this for hours, scouring Google to no avail, the above solution worked for me. Thanks @laurencefass I'm doing something like this:
// Some Next JS Page export const getStaticProps = async () => { return { props: { openGraphData: [ { property: "og:image", content: "https://glievsbwngosqvrxtupy.supabase.co/storage/v1/object/public/event-banners/Jul%208%20Darkest%20Hour%20LONG.jpeg?t=2022-06-28T21%3A47%3A43.910Z", key: "ogimage", }, { property: "og:image:width", content: "400", key: "ogimagewidth", }, { property: "og:image:height", content: "300", key: "ogimageheight", }, { property: "og:url", content: `http://foobar.com/events`, key: "ogurl", }, { property: "og:image:secure_url", content: "https://glievsbwngosqvrxtupy.supabase.co/storage/v1/object/public/event-banners/Jul%208%20Darkest%20Hour%20LONG.jpeg?t=2022-06-28T21%3A47%3A43.910Z", key: "ogimagesecureurl", }, { property: "og:title", content: "Hey hey", key: "ogtitle", }, { property: "og:description", content: "Ima description", key: "ogdesc", }, { property: "og:type", content: "website", key: "website", }, ], }, }; };Then in
_app.tsx:function MyApp({ Component, pageProps }: AppProps) { console.log("pageProps", pageProps); const { openGraphData = [] } = pageProps; return ( <> <Head> {openGraphData.map((og) => ( <meta {...og} /> ))} </Head> <Component {...pageProps} /> </> ); } export default MyApp;
this is the only solution that worked for me. kudos! didn't know the props pass from child to _app.js in getStaticProps
Hey all, for what it's worth, I had no luck originally with next-seo, so reverted to my above solution. One way to test that your values are getting passed into _app.js (maybe mentioned above), is by opening your browser's "View Source" and inspecting the <head> for passed in values (as opposed to just viewing in the browser's dev tools DOM inspector).
@adamjarling , you can use next-seo on the _app.js and then use getStaticProps on other pages and pass the required props. That way, when it renders, it'll go through _app.js so you're meta will get picked from there. If you place the meta in individual pages and have a seperate meta set in _app.js your app will override it.
we can actually close this issue because @adamjarling's solution is the fix for this
the workarounds suggested here do not fix the problem with Next code which does not work as documented for some metadata consumers like facebook. please do not close this issue based on a workaround. either the documentation or the core code is broken.