Next.js Discord

Discord Forum

Dynamic metadata - client page

Unanswered
Yellowstripe scad posted this in #help-forum
Open in Discord
Yellowstripe scadOP
Hello, I'm trying to add dynamic metadata to client page.tsx.
Page is rendering blog post where we're fetching data from API with specific ID.
This specific ID or post title I would like to set as metadata title.
I've read that its necessary to use layout.tsx for every client page so I've created them and they're working fine as static one but how can I add dynamic data there?
blog structure:
- app / blog (page , layout) / [id] (page, layout)
'use client';
import { useParams } from 'next/navigation';
import Image from 'next/image';
import Link from 'next/link';
import { Suspense, useEffect, useState } from "react";
import { dataURL, fetchData } from '@/lib/fetchData';

const PostPage = () => {
  const [post, setPost] = useState<PostType>({} as PostType)
  const [total, setTotal] = useState<number>(0 as number)
  const fallback = <div className='flex items-center justify-center'>Loading...</div>
  const params = useParams()
  const postImageUrl = `https://picsum.photos/id/${params.id}/1500/400.jpg`;  
    
  const getPostData = async () => {
    try {
      const data = await fetchData(`${dataURL}/${params.id}`)
      const {total} = await fetchData(`${dataURL}`)

      setPost(data)
      setTotal(total)
    } catch (error) {
      console.error(error)
    }

  }

  useEffect(() => {
    getPostData()
  }, [])
  
  return (picture)
}

export default PostPage;

post page layout.tsx
export default function PostPageLayout({ children }: LayoutProps) {
  return [children]
}

31 Replies

Giant panda
First of all the page has nothing that would justify having it a client page and is needlessly complex. So you should consider making it fully server-side and dropping most of the overhead.
Second, answering your actual question: metadata can only be part of server-side pages i.e. in a server component.
What people do is to handle metadata server-side and render a "client page component" for the rest.
@Giant panda First of all the page has nothing that would justify having it a client page and is needlessly complex. So you should consider making it fully server-side and dropping most of the overhead.
Yellowstripe scadOP
Thanks, so what exactly should I rework here? How should I make it server-side if most of logic (fetching, useEffect) needs hooks? Should I pass that data from /blog into [id] ?
Giant panda
You can just fetch server-side and don't need hooks at all
async function Page() {
  const response = await fetch(...);
  const data = await response.json();
  return <Post data={data} />;
}
That's the whole point of server components
@Giant panda That's the whole point of server components
Yellowstripe scadOP
I understand that, I've tried that but I was unable to solve issue of fetching specific post data like fetch(${url}${id}). The Blog page itself looks very simillar tho :/
Giant panda
If the ID for example is obtainable from the path segments or search params the page gets them via the context object
@Giant panda If the ID for example is obtainable from the path segments or search params the page gets them via the context object
Yellowstripe scadOP
Alright, you were 100% right to the Post page. I've fixed that so its server-side now. I'm getting post [id] from props.
I'm wondering if its also possible to refactor Blog page where I'm rendering 10 posts and have button with loadMore (+10)
Giant panda
Yes.
You'd have a client side transition which updates the search params and the page would server-side load X posts based on the limit search param (or a default value in case it's not defined)
@Giant panda You'd have a client side transition which updates the search params and the page would server-side load X posts based on the limit search param (or a default value in case it's not defined)
Yellowstripe scadOP
Hm, i've refactored blog page to server-side but I guess Its impossible to have it as server-side with onClick loadMore functionality 🤔 I want to load 10 more posts on click. Previously I was handling that with useEffect hook.
Giant panda
Make the button a client component and let it update the URL on click
Yellowstripe scadOP
I'm unable to do it, I'm not sure how that button component should look like and how it should work with Blog page :/ I've tried to refactor button and use it as client but without success.
I had to include experimental serverActions to next config:
js 
experimental: {
        serverActions: true
 },


ButtonLoadMore:
'use client'

import React, {ReactNode} from 'react'

const ButtonLoadMore = ({ children, className, loadMoreHandler }: { children: ReactNode, className: string, loadMoreHandler: () => Promise<void>}) => {
  return <button className={className} onClick={async () => {await loadMoreHandler()}}>{children}</button>
}

export default ButtonLoadMore;


Blog page (img)
I'm recieving this error so far
I'm not sure how that Button component should update URL. I dont see it possible without useEffect 🤔
Giant panda
Conceptually you are mixing many different things here.
First of all your loadMoreHandler is just a function and not a server action yet
That's why you can't pass it down to a client component
Second I would redo this entirely
As an inspiration here is a snippet from one of my apps
The idea is that tabs exist to toggle modes that are reflected in the search params of a URL
Which the page can read to alter its data fetching behavior
'use client';

export function CopiesTypePicker() {
  const { replace } = useRouter();
  const pathname = usePathname();
  const [isLoading, startTransition] = useTransition();

  function onChange(value: string) {
    const searchParams = new URLSearchParams(window.location.search);
    searchParams.set('copiesType', value);
    startTransition(() => {
      replace(`${pathname}?${searchParams.toString()}`);
    });
  }

  return (
    <Tabs onValueChange={onChange}>
      {/* ... */}
    </Tabs>
  );
}
In a similar fashion you could increase the limit param in the search params by X on each click
The server-side page would then just validate that and fetch something like Math.min(parseInt(limit, 10) ?? 10, 50) entries or so
No server actions needed since it's just querying with new search params
Server actions themself are more of a concept for mutating data
Think of POST/PUT requests
Yellowstripe scadOP
Hm, so in the first place I guess i would need to overwrite URL of /blog page to query /blog?limit=10 and then overwite it on every click
orr that basically doesnt matter since it would be updated on click