#How to set metadata in client rendered pages (Next 13 app dir)?

40 messages · Page 1 of 1 (latest)

regal edge
#

I am running into a few issues around the Server Components in Next 13 using the app directory.
Since I use a theme provider in my root layout, I get the following error if I don't add use client to all my pages:

TypeError: createContext only works in Client Components. Add the "use client" directive at the top of the file to use it. Read more: https://nextjs.org/docs/messages/context-in-server-component

But with adding use client to all my pages I can then not use the new metadata object or generateMetadata function, as I get

The Next.js 'metadata' API is not allowed in a client component.

I feel like I must be missing something, but so far it seems like opting out of server rendered components seems to be required for all of my pages and my layout due to the use of context, state and/or effects. What am I missing?

dawn pulsar
#

It’s bad practice to make everything client components

regal edge
#

Thanks @dawn pulsar .
I figured it's a bad practice to make everything a client component, but it just doesn't work. I tried the LayoutClient approach, but it's not working for me. Not sure why?

layout-client.tsx:

'use client';

import { ThemeProvider } from 'next-themes';
import { PropsWithChildren } from 'react';

export default function LayoutClient({ children }: PropsWithChildren) {
    return <ThemeProvider attribute="class">{children}</ThemeProvider>;
}

layout.tsx

import LayoutClient from './layout-client';

import { SkipToContent } from '@components/atoms/SkipToContent';
import { Footer } from '@components/organisms/Footer';
import { Header } from '@components/organisms/Header';

import '@styles/globals.css';

import { PropsWithChildren } from 'react';

export default function RootLayout({ children }: PropsWithChildren) {
    return (
        <html lang="en">
            <head>
                <link
                    rel="icon"
                    type="image/png"
                    sizes="32x32"
                    href="/assets/favicon.ico"
                />
            </head>
            <body>
                <LayoutClient>
                    <SkipToContent />
                    <Header />
                    <main id="content">{children}</main>
                    <Footer />
                </LayoutClient>
            </body>
        </html>
    );
}
dawn pulsar
#

In the linked example layout.tsx is a server component and layout-client.tsx is a client component

regal edge
#

Ah sorry, just copied this incorrectly. My layout.tsx doesn't have use client at the top, just double checked

#

(updated)

dawn pulsar
#

Got it. Then what didn’t work?

regal edge
#

I'm still getting

TypeError: createContext only works in Client Components. Add the "use client" directive at the top of the file to use it. Read more: https://nextjs.org/docs/messages/context-in-server-component
dawn pulsar
#

Do you use any kinds of providers or contexts elsewhere?

regal edge
#

No, just the theme provider

#

Actually looking through the call stack it seems to be caused by framer motion:

(sc_server)/./node_modules/.pnpm/[email protected]_biqbaboplfbrettd7655fr4n2y/node_modules/framer-motion/dist/cjs/index.js

I guess it must be setting up its own context

dawn pulsar
#

Framer motion requires client components

#

Anything that involves interacting with browser api requires client components

regal edge
#

Yea, that makes sense, so in that case my only option is to use client components for everything, or drop framer motion?

dawn pulsar
#

Treat framer motion like yet another context provider?

regal edge
#

Yea, I guess I could create a component for each element I currently use framer motion for. Gonna be quite a refactor though.

#

Just removed all use of framer motion to see if that solves the issue, and now getting this very cryptic error:

#

Any idea where this might be coming from?

dawn pulsar
dawn pulsar
regal edge
#

Where though? I stripped the page back to the bare minimum:

import { Container } from '@components/atoms/Container';

export const metadata = {
    title: 'Home',
};

const HomePage = () => {
    return (
        <section id="home" className="h-full snap-center">
            <Container>
                <h1 className="headline mt-20 text-3xl md:text-5xl lg:text-6xl">
                    Hello World
                </h1>
                <p className="my-8 text-lg">
                    Ullamco esse ipsum in tempor reprehenderit in dolore veniam labore incididunt. Et nulla cillum dolor minim esse non mollit dolore tempor excepteur duis voluptate anim elit. Enim sit mollit dolore cillum consequat officia esse anim proident. Occaecat exercitation est eu aliquip minim in sunt duis Lorem laborum cillum eu do. Anim duis proident sint dolore consequat aliquip aliqua eu do id.
                </p>
            </Container>
        </section>
    );
};

export default HomePage;
regal edge
#

It's just a utility component with some styles:

import { PropsWithChildren } from 'react';

const Container = ({ children }: PropsWithChildren) => {
    return <div className="mx-auto w-full px-4 md:max-w-5xl">{children}</div>;
};

export { Container };
dawn pulsar
#

That is odd, this should work - i see nothing wrong

#

Check for all components you make and see if they return something

regal edge
#

All of them return something, just went though them all. Also went through everything used in the Header component to make sure anything using framer motion there is set to use client

dawn pulsar
#

That is very weird

#

Try this: make a brand new app, then copy your files over one at a time

#

It will eventually break at some point

#

Then the buggy file is the file you move right before that point

regal edge
#
<AnimatePresence>
    {isOpen ? (
        <motion.div
            initial={{ opacity: 0, y: '-50%', x: 0 }}
            animate={{ opacity: 1, y: 0, x: 0 }}
            exit={{ opacity: 0, y: '-50%' }}
            transition={{ duration: 0, delay: 0 }}
        >
            <FocusTrap
                focusTrapOptions={{
                    clickOutsideDeactivates: true,
                }}
            >
                <ul>
                    {/* {navItems.map(({ href, title }, i) => (
                        // <NavigationItem
                        //     key={href}
                        //     href={href}
                        //     title={title}
                        //     variants={navigationVariants}
                        //     initial="hidden"
                        //     animate="visible"
                        //     customDelay={0.5 + (i + 1) * 0.1}
                        // />
                    ))} */}
                    <motion.li
                        className="flex justify-center mt-12"
                        variants={navigationVariants}
                        initial="hidden"
                        animate="visible"
                        // custom={0.5 + (navItems.length + 1) * 0.1}
                    >
                        <ThemeToggle />
                    </motion.li>
                </ul>
            </FocusTrap>
        </motion.div>
    ) : null}
</AnimatePresence>
#

Mh so I've dug through my components and it appears to be caused by framer motion in my mobile menu (see above)

#

With the lines above commented out it works, if I re-add those, I get

ReferenceError: Cannot access 'Header' before initialization
#

Should mention that both the Header and also the MobileMenu are set to use client

regal edge
#

I found a solution: essentially swapping the layout and layout-client by moving the header into the layout-client. That way the layout itself essentially just wraps children with the LayoutClient component.

#

Not sure if this is ideal, but it resolves the issues I had with framer motion in the header components

rustic sequoia