vercel / next.js

The React Framework
https://nextjs.org
MIT License
124.94k stars 26.69k forks source link

next/head <meta> tags for pages overriding default _app.js <meta> tags are rendered in the browser but not visible to facebook debugger. #35172

Closed laurencefass closed 7 months ago

laurencefass commented 2 years ago

Verify 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 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 section into _app.js file and a 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.

laurencefass commented 2 years ago

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?

GabenGar commented 2 years ago

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.

PorterK commented 2 years ago

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

GabenGar commented 2 years ago

Again, without seeing the structure of _app it's hard to tell the source of the problem.

PorterK commented 2 years ago

Again, without seeing the structure of _app it'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!

laurencefass commented 2 years ago

@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?

laurencefass commented 2 years ago

@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 commented 2 years ago

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

laurencefass commented 2 years ago

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?

ahalaburda commented 2 years ago

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.

GabenGar commented 2 years ago

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.

laurencefass commented 2 years ago

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?

laurencefass commented 2 years ago

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?

GabenGar commented 2 years ago

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.

laurencefass commented 2 years ago

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 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 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>
  </>
adamjarling commented 2 years ago

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;
JStevens127 commented 2 years ago

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!

laurencefass commented 2 years ago

The example is oversimplified and th keys dont seem to be working as expected in cases stated here.

lakindu2002 commented 2 years ago

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

GabenGar commented 2 years ago

@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.

lakindu2002 commented 2 years ago

yeah i am using next seo. but the _app.js overiddes all child page next-seo tags

lakindu2002 commented 2 years ago

@GabenGar

laurencefass commented 2 years ago

@lakindu2002 did you report that in the next-seo issue queue?

lakindu2002 commented 2 years ago

@laurencefass i dont think its an issue with next-seo

lakindu2002 commented 2 years ago

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

lakindu2002 commented 2 years ago

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

adamjarling commented 2 years ago

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

lakindu2002 commented 2 years ago

@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.

lakindu2002 commented 2 years ago

we can actually close this issue because @adamjarling's solution is the fix for this

laurencefass commented 2 years ago

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.

GabenGar commented 2 years ago

There is no problem with the next code, your component with meta tags just doesn't render on server for whatever reason. Most likely because it is wrapped in client-only context provider.

laurencefass commented 2 years ago

"for whatever reason"... And that, there is the problem. Next js behaviour does not match documentation for a very clearly outlined and simple test case. I can make it work in other ways, but not in the documented manner. I havent wrapped the code in any client context provider only the code outlined in this post.

GabenGar commented 2 years ago

It's not next's problem though. And nothing is ever "clear" or "simple" in regards to SEO, and that's by design.

laurencefass commented 2 years ago

I read some earlier comments: "...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 have specifically isolated the test to the simplest possible case and there is no conditional rendering. Just a page component rendering through _app.ts. That is not working. This functionality doesnt match the documentation.

GabenGar commented 2 years ago

At this point you have to provide a reproduction repo instead of relying on "simplified examples" and blaming nextjs. Works on my site just fine, you can look at page source and see all the tags there.

laurencefass commented 2 years ago

I referred to a working site demonstrating the problem and provided the "simple code" running on that site. It is an isolated test case and far simpler test case than your own site which also doesnt prove it works as the issue is related to facebook scraping. The server is not running at present I will run it again next week and hope someone else picks up the issue.

I provided a workaround that I can use but its not the documented approach., All the code I have used is referenced in this post and can be very easily reproduced on a new Next JS site with no additional coding required other than that alreaduy provided so it is reproducible. As a comparison I tried the documented approach for Remix SEO and it just works with no unexpected outcomes. I maintain that Next JS is not working according to documentation I am not "blaming" Next JS, that is a rather strange remark.

May I ask are you are a Next JS maintainer or another consumer of Next JS services like myself? If you are not I am happy to discontinue this conversation as you're replies and tone are a bit unexpected... "relying on simplified examples and blaming Next JS" ??? SEO is complex by design ??? It must be conditional rendering or context wrappers ??? If you are not directly maintaining Next JS SEO Im happy for this to remain open for someone from core to pick it up.

No disrespect intended. Thanks

GabenGar commented 2 years ago

Facebook doesn't do any magic in regards to meta tag parsing. I do remember looking at your examples when they were online and the tags were completely absent from the initial html. Which means the problem is in your config/code/component. But instead of trying to find the source of the problem for over a month you blame nextjs for forcing you into using some hacks. Also github highlights repo maintainers the same way you have Author flair on all your posts on this issue. So you can take a gander if I am maintainer or not. Also a maintainer will ask for a reproduction repo.

laurencefass commented 2 years ago

Yet more references to "blaming" Next JS? Please can you stop trolling as you are clouding this issue for others who also clearly share the same "problem". The config/code/component is here: https://github.com/vercel/next.js/issues/35172#issuecomment-1076097776. That is all I have in a new Next JS site and quite easilty reproducible. If you can see the issue in the code provided feel free to contribute, else perhaps please respectfully "gander" elsewhere. Thank you.

Mouldi commented 2 years ago

Can someone from the nextjs team please fix this issue? Also rendering meta tags on the client side seems to not work for most social media sites. Rendering it on the server side gets picked up correctly. Is there a way to render the meta tags dynamically on the server side (_document.js) ? Thank you

kamikaz1k commented 2 years ago

@laurencefass @Mouldi FWIW, I am running next@12.1.6 and it works for me as documented.

Funnily enough, I found this issue when it Wasn't working for me. But for some reason or another, it now works...

If you have your code up again, I can also try to have a look to see what the difference between our implementations are. But seems to be working as advertised!

ltranco commented 2 years ago

Current next version: "next": "^11.1.3"

The problem for me was like @GabenGar mentioned: I had some conditional logic in my component.

My component with dynamic route is in pages/article/[slug].tsx, where I used to fetch the article using the slug on component mount. I used to use useRouter to grab the slug in the URL → pass the slug to another react-query hook to fetch the article (useFetchArticle). This hook returns states like isLoading, so I used to do

if(isLoading) {return <LoadingComponent/>}

This resulted in all of my overriding open graph tags to not show up. I changed the logic to fetching the article in getServerSideProps and removed all conditions in the component and everything worked.

I also used the next/head component to override the open graph tags defined in _app.tsx

<Head>
        <title>{title}</title>
        <meta property="og:image" content={hero_image} key="image" />
        <meta property="og:title" content={title} key="title" />
        <meta property="og:description" content={preview} key="description" />
      </Head>
balazsorban44 commented 2 years ago

I ask everyone to read through our Code of Conduct for clarity. :green_heart:

@laurencefass could you link to a reproduction repository with the source code so we can have a closer look? (before doing so, make sure you have tested next@canary since it can include fixes for previous issues if that's the case. You pointed out that next info did not work, which indicates you might be on an older version)

michaelangeloio commented 2 years ago

Thank you @laurencefass, for posting this! You are not alone, I am having the same issue. Commenting to stay in the loop.

Chamberlainfrancis commented 1 year ago

You are not alone @laurencefass just commenting to stay in the loop incase a proper solution comes up.

esp-phudang commented 1 year ago

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!

this resolve my problem, thankssss

DestinyJunior commented 1 year ago

@laurencefass your solution works like charm, thanks.....

AlbinoGeek commented 1 year ago

Also hoping for a proper solution, not just forcing all pages to lambda-render.

johnpolacek commented 1 year ago

I just got bit by this as well. In my case, I could view source (like the old days!) and see that no matter what I did the meta tags in _document.tsx would always win. Thanks @laurencefass for showing the way. It seems _document.tsx is not aware of the updated context when rendering meta tags on the server. However, _app.tsx is aware and can handle rendering meta tags for each page.

Seems like a not-in-the-docs footgun to me, so in case it helps, for me removing meta tags from _document.tsx and putting them in _app.tsx did the trick.

// _app.tsx
import type { AppProps } from "next/app";
import Head from "next/head";

const defaultTitle = "Your Default Title";
const defaultDescription = "Your Default Description";
const defaultImage = "https://example.com/default-image.jpg";

interface OpenGraphDataItem {
  property?: string;
  name?: string;
  content: string;
}

const defaultOpenGraphData = [
  { property: "og:type", content: "website" },
  { property: "og:site_name", content: defaultTitle },
  { property: "og:description", content: defaultDescription },
  { property: "og:title", content: defaultTitle },
  { property: "og:image", content: defaultImage },
  { name: "twitter:card", content: "summary_large_image" },
  { name: "twitter:title", content: defaultTitle },
  { name: "twitter:description", content: defaultDescription },
  { name: "twitter:image", content: defaultImage },
];

function MyApp({ Component, pageProps }: AppProps) {
  const { openGraphData = defaultOpenGraphData, title, description } = pageProps;

  return (
    <>
      {openGraphData.length > 0 && (
        <Head>
          <title>{title || defaultTitle}</title>
          <meta name="description" content={description || defaultDescription} />
          {openGraphData.map((og: OpenGraphDataItem) => (
            <meta key={og.property || og.name} {...og} />
          ))}
        </Head>
      )}
      <Component {...pageProps} />
    </>
  );
}

export default MyApp;

Use the defaults for most pages, then when you need it you can do something like:

import { GetServerSideProps, NextPage } from "next"
import { getDataFromSomewhere } from "../lib/getDataFromSomewhere"

type Props = { data: any; id: string; openGraphData: any[] };

const ExamplePage: NextPage<Props> = ({ data, id, openGraphData }) => {
  return (
    <div>
      {/* Your page content */}
    </div>
  )
}

export const getServerSideProps: GetServerSideProps = async ({ query }) => {
  const { id } = query;
  const data = id ? await getDataFromSomewhere(id as string) : null;

  let title = "Your Default Title";
  let description = "Your Default Description";
  let imageUrl = "https://example.com/default-image.jpg";

  if (data) {
    title = `${data.title} - Example`;
    description = `${data.title} - ${data.plot} (example description)`;
    imageUrl = `${data.poster}`;
  }

  const openGraphData = [
    { property: "og:title", content: title },
    { property: "og:description", content: description },
    { property: "og:image", content: imageUrl },
    { name: "twitter:title", content: title },
    { name: "twitter:description", content: description },
    { name: "twitter:image", content: imageUrl },
  ];

  return { props: { data, id, openGraphData } };
};

export default ExamplePage;
AlbinoGeek commented 1 year ago

Even with the workarounds posted in this thread, I cannot get media previews working for any of my NexTJS websites using next-seo, or even hand-entered per page meta. Only the meta in my _app and _document show when viewing soure. This makes it nearly impossible to have a NextJS website with SEO.

image

The tags appear to be populated by javascript after load, making them nearly useless for search engines.

I cannot delete all the meta in _app (for one of these workarounds), since NextJS requires your viewport be set there, and absolutely refuses to allow you to set it in _document (or pages for that matter) **

Warning: viewport meta tags should not be used in _document.js's <Head>. https://nextjs.org/docs/messages/no-docume
nt-viewport-meta
Warning: viewport meta tags should not be used in _document.js's <Head>. https://nextjs.org/docs/messages/no-docume
nt-viewport-meta
Warning: viewport meta tags should not be used in _document.js's <Head>. https://nextjs.org/docs/messages/no-docume
nt-viewport-meta

The getServerSideProps route would force all pages to be dynamically rendered, significantly reducing performance and completely disabling the ability to statically render the site as a PWA.

How can this possibly be an acceptable outcome?

To quote the build log, when building without standalone:

Route (pages)                                             Size     First Load JS
┌ λ /                                                     1.74 kB         245 kB
├   /_app                                                 0 B             243 kB
├ λ /pricing                                              3.18 kB         249 kB
├ λ /profile/[id]                                         15.4 kB         275 kB
├ λ /ref                                                  253 B           292 kB
├ λ /ref/[id]                                             213 B           292 kB
├ λ /server-sitemap.xml                                   257 B           243 kB
└ λ /test                                                 2.19 kB         245 kB

... [all pages were forced server-side rendered]

λ  (Server)  server-side renders at runtime (uses getInitialProps or getServerSideProps)

When building with standalone/export:

Error occurred prerendering page "/". Read more: https://nextjs.org/docs/messages/prerender-error
Error: Error for page /_app: pages with `getServerSideProps` can not be exported. See more info here:
 https://nextjs.org/docs/messages/gssp-export
    at .\node_modules\next\dist\export\worker.js:333:23
    at async Span.traceAsyncFn (.\node_modules\next\dist\trace\trace.js:79:20)

To quote the documentation


Further, _document requires a <Head> even if it's empty:

Your custom Document (pages/_document) did not render all the required subcomponent.
Missing component: <Head />

Is static export deprecated, or is this a bug in need of fixing to restore a supported feature?

SumitMayani123 commented 1 year ago

import { Fragment, useEffect, useState} from "react"; import { Provider } from "react-redux"; import { store } from "@Wrteam/store/store"; import { settingsLoaded } from "@Wrteam/store/reducer/settingsSlice"; import Head from "next/head"; import PreLoader from "@Wrteam/layout/PreLoader"; import ScrollToTop from "@Wrteam/components/ScrollToTop/ScrollToTop"; import axios from "axios"; import webData from "@Wrteam/utils/config"; import Drift from "@Wrteam/components/Drift/Drift" import { ToastContainer } from "react-toastify";

// all css import "@/css/bootstrap.min.css"; import "@/fonts/flaticon/font/flaticon.css"; import "@/fonts/font-awesome/css/all.min.css"; import "@/fonts/themify-icons/themify-icons.css"; import "@/css/animations.min.css"; import "@/css/ionicons.min.css"; import "@/css/style.css" import "react-toastify/dist/ReactToastify.css"; import "react-loading-skeleton/dist/skeleton.css"; import "@/css/responsive.css";

function MyApp({ Component, pageProps, data }) {

const [loader, setLoader] = useState(true);

useEffect(() => { setTimeout(() => { setLoader(false); }, 1000); }, []);

useEffect(() => { settingsLoaded(""); }, []);

return (

{data ? data.seo_site_title : "WRTeam"} {loader && }

); }

MyApp.getInitialProps = async ({Component, ctx }) => { const { req } = ctx; let data = {};

try { const response = await axios.get(${webData.API_URL}settings, { headers: { cookie: req ? req.headers.cookie : undefined // Pass the request's cookies to the API call if running on server side } }); data = response.data.data; } catch (error) { console.log(error); }

return {data}; };

export default MyApp;