Next.js Discord

Discord Forum

Next JS Dynamic Metadata help

Answered
Ocicat posted this in #help-forum
Open in Discord
Avatar
OcicatOP
To my understanding You can generate metadata in both page and layout
If there is a layout with matadata at
/app/(content)/layout.tsx

import React from 'react'
import Navbar from '../components/Navbar'
import "../globals.css"
import QueryProvider from '../components/query-provider';

interface Metadata {
  title: string;
  description: string;
  image: string;
  url: string;
}
export const metadata = {
    title: '***',
    description: 'text',
    image: '/images/image.png', // Add the path to your image
    url: 'website/home', // Add your website URL
  }

  interface LayoutProps {
    children: React.ReactNode;
    metadata: Metadata;
  }

  export default function Layout({ children, metadata }: LayoutProps) {
 return (...)

I have a page at a deeper level it will overwrite the metadata.
Its doing that but unfortunately the values are undefined.
app/(content)/home/communities/[id]/page.tsx

import type { Metadata as NextMetadata } from 'next';
interface Metadata extends NextMetadata {
  image: string;
  url: string;
}

type Props = {
  params: {
    id: string;
    communityName: string;
    image: string;
  }
}

export const generateMetadata = ({ params }: Props): Metadata => {
  return {
    title: `Text: ${params.communityName}`,
    description: 't',
    image: params.image,
    url: `https://website/home/communities/${params.id}`,
  }
}

interface CommunityData {
  id: string;
  timestamp: any; // Adjust the type based on your actual data structure
  tags: string;
  link: string;
  [key: string]: any; // This allows for additional properties
}
export default async function community({ params }: Props ) {
  const db = getFirestore(app);
  let data: CommunityData = { id: '', timestamp: '', tags: '', link: '' };
  const querySnapshot = await getDoc(doc(db, 'communities', params.id));
  data = { ...querySnapshot.data(), id: querySnapshot.id } as CommunityData;

  console.log(data.communityName);
  console.log(data.description);

  return (...)
Answered by Ocicat
I kind of fix it.
I'm getting the title page working but all the other fields are not working.
In generateMetadata
export const generateMetadata = async ({ params }: Props): Promise<Metadata> => {
//get db code
  return (
    //data to return 
  )
} 
View full answer

43 Replies

Avatar
OcicatOP
Any assistance is appreciated I was not able to write the formalities due to the character limit.
Welcome to all help
Thank you.

This is also the error I am facing
 app/(content)/layout.tsx (30:26) @ title
 ⨯ TypeError: Cannot read properties of undefined (reading 'title')
    at Layout (layout.tsx:31:44)
    at stringify (<anonymous>)
digest: "3812174722"
  28 |     <html lang="en">
  29 |             <head>
> 30 |         <title>{metadata.title}</title>
     |                          ^
Avatar
I haven't tried this pattern yet, but it looks like the parent metadata is provided on the the 2nd parameter of the generateMetadata function:
Image
oh wait sorry I'm misunderstanding your question
Avatar
okay so you are not able to retrieve the raw metadata on the default export of a layout.tsx file.

the head tag is automatically generated by next from the values of metadata/generateMetadata so you won't need to worry about setting the actual title or anything related to metadata, unless you need to set your own tags on the head element.
Avatar
OcicatOP
My issue is
Page.tsx is not sending the metadata so Layout.tsx can display it.

Page.tsx will overwrite whats already on layout as its deeper in the folder hierarchy.

In layout.tsx I have static text but in page.tsx its dynamic. I am exporting it too.
I know its probably a wrong implementation of something or something missed on my end.
Avatar
so the pages are trying to change the title of the metadata but layout.tsx sets it as a static text?
or if you need to overwrite layout's title metadata, you could use the [absolute field](https://nextjs.org/docs/app/api-reference/functions/generate-metadata#absolute) instead
Avatar
OcicatOP
I can't use any of those examples as its static.
I need Dynamic and find out why its not getting passed to layout from page even tho its defined and generated there.
Avatar
you could do the same thing with generateMetadata, it returns the same type as the metadata object: Metadata
Avatar
OcicatOP
I have this
in page.tsx
export const generateMetadata = ({ params }: Props): Metadata => {
  return {
    title: `Text: ${params.communityName}`,
    description: 't',
    image: params.image,
    url: `https://website/home/communities/${params.id}`,
  }
}
Avatar
here, do this:
layout.tsx
export const metadata = {
  title: {
    template: "%s MyAwesomeSite"
  },
  description: 'text',
  image: '/images/image.png',
  url: 'website/home',
}

page.tsx
export const generateMetadata = ({ params }: Props): Metadata => {
  return {
    title: `Text: ${params.communityName}`,
    description: 't',
    image: params.image,
    url: `https://website/home/communities/${params.id}`,
  }
}
or if you don't want to specify a template on layout.tsx, you could use the absolute field:
export const generateMetadata = ({ params }: Props): Metadata => {
  return {
    title: {
      absolute: `Text: ${params.communityName}`,
    },
    description: 't',
    image: params.image,
    url: `https://website/home/communities/${params.id}`,
  }
}
please do read the docs
Avatar
OcicatOP
Same problem
Avatar
is it this same error?
you're not supposed to set the title page on layout.tsx, it's already managed by next through metadata
Avatar
OcicatOP
Yes exacly the same
app/(content)/layout.tsx (32:26) @ title
 ⨯ TypeError: Cannot read properties of undefined (reading 'title')
    at Layout (layout.tsx:33:44)
    at stringify (<anonymous>)
digest: "2754771648"
  30 |     <html lang="en">
  31 |             <head>
> 32 |         <title>{metadata.title}</title>
     |                          ^
Avatar
next doesn't give you the raw metadata as props to the layout, it's kept by next and is used to generate the head by itself, you don't need to do anything
Avatar
OcicatOP
Yes I see layout.tsx has metadata
But if there's metadata made deeper in the directory it should overwrite it.
in this case I have page.tsx generating metadata.
Avatar
yes, next already handles that
you don't need to worry about metadata, next already handles that
Avatar
OcicatOP
Yes
You are correct
Avatar
all you need to do is to remove the title tag and metadata taken from LayoutProp
Avatar
OcicatOP
testing
Avatar
OcicatOP
I chaged layout.tsx to
import React from 'react'
import Navbar from '../components/Navbar'
import "../globals.css"
import QueryProvider from '../components/query-provider';

export const metadata = {
    title: '***',
    description: 'text',
    image: '/images/image.png', // Add the path to your image
    url: 'website/home', // Add your website URL
  }


export default function Layout({
  children,
}: {
  children: React.ReactNode
})  {
 return (...)


Still getting undefined
This allows page to generate the metadata
Which is a step forward.
But what ever page.tsx produces is undefined.
They work on the page.
They are retrieved from a DB
Avatar
please be specific, what's returning undefined?
Avatar
OcicatOP
export const generateMetadata = ({ params }: Props): Metadata => {
  return {
    title: `Text: ${params.communityName}`,
    description: 'text',
    image: params.image,
    url: `https://website/home/communities/${params.id}`,

  }
}


Generatemetadata from page.tsx
Avatar
??
I don't see any spots an undefined could show up there
do you mean params.communityName evaluates into undefined?
from what I could tell from the code you shared above, communityName should come from a dynamic route
please do make sure that communityName is defined as a dynamic route represented as a folder with brackets .../[communityName]/... around them, and you have the page.tsx file nested inside that folder
Avatar
OcicatOP
Yes
do you mean params.communityName evaluates into undefined?
Avatar
OcicatOP
generateMetadata is a static function that runs before the component is rendered. Because of this, it does not have direct access to the props that your component receives after fetching data in the component itself.
Avatar
they clearly state that generateMetadata is being passed [the params prop](https://nextjs.org/docs/app/api-reference/functions/generate-metadata#parameters)
Image
then you seem to be having an incorrect folder structure
you have to have a folder with the exact naming [communityName] that hosts the file the page.tsx either directly inside of the folder, or inside of the subfolders inside that same folder
Avatar
OcicatOP
I kind of fix it.
I'm getting the title page working but all the other fields are not working.
In generateMetadata
export const generateMetadata = async ({ params }: Props): Promise<Metadata> => {
//get db code
  return (
    //data to return 
  )
} 
Answer
Avatar
OcicatOP
Thank you for your help I will mark this as answer as it shows signs of meta data being generated to some extent.

Helped me to understand how the layout and page works in more detail I appreciate your time.
Thank you.
Avatar
good luck 👍 next time i'd suggest to clarify your questions, i was so confused as to what the problem really is