Next.js Discord

Discord Forum

Waiting a specific data to load before rendering the whole page

Unanswered
Hunting wasp posted this in #help-forum
Open in Discord
Hunting waspOP
So, I have a navbar that changes based on the user, to do that I retrieve the session from nextAuth after the authentication and use the username as a parameter to query a mongodb database.

I use a fetch() method to retrieve the data.

The thing is, for how it works right now it renders the website and, after some seconds, the navbar, which is a weird behavior.

I need the whole website to wait before presenting itself to the user.

What I have tried so far it's to implement Server Side Props but this has given me a new problem, the array fetched from the API route it's seen as undefined even tho I do declare the type inside the method that fetches the data.

The code can be found attached to this post (the commented part is how it used to work before):

and this is the error I receive:
⨯ TypeError: Cannot read properties of undefined (reading 'map')
this happens when it tries to iterate over the listMenuItems.

Now, not sure at what is causing this error nor if server side props it's the right way to implement the solution for what I need

88 Replies

export const getServerSideProps = (async (ctx) => {
  const response = await getServerSession(ctx.req, authConfig)
  const listMenuItems: MenuItem[] = // render item 
  return { props: { listMenuItems } }
}) satisfies GetServerSideProps<{
  listMenuItems: MenuItem[]
}>
you can just fetch the data inside getServerSideProps
if you want to fetch own api route, you need to pass absolute url to it
also, are you using page router or app router? because I see 'use client' on the top which is for app router
and getServerSideProps only work for page component which is under the pages folder
Hunting waspOP
I am using app router. So server side props isn't a viable solution. What would be then a better solution?
if you are using app router, you can fetch the data directly in the Navbar component
remove 'use client' to make it server component
render any client component if you need user interaction and pass data from server component
Hunting waspOP
the 'use client' was because I was using before useState and useEffect, what would be the alternative to that?
You meant doing the mongodb call directly from the navbar component?
it mark the component to be a client component and you cannot do server side stuff in a client component
Hunting waspOP
Yeah I know what use client it's for, but when using react hooks I am kinda force to use "use client" otherwise the application breaks, what would be an alternative to useEffect and useState?
@Ray render any client component if you need user interaction and pass data from server component
like i said, create another file and put 'use client' on top then import to the server component
import Link from "next/link";
import DropdownMenu from "../dropdown/DropdownMenu";

const Navbar = async () => {
  const listMenuItems = await fetchMenuItems();

  return (
    <header className="flex gap-10 items-center bg-textColor py-4 px-2">
      <div className="flex gap-8 items-center text-white m-2.5">
        {listMenuItems.map((item) => {
          return typeof item.children !== "undefined" ? (
            <DropdownMenu item={item} />
          ) : (
            <Link className="" href={item?.route || ""}>
              {item.title}
            </Link>
          );
        })}
      </div>

      <ClientComponent data={listMenuItems} />
      <ClientComponent2 data={listMenuItems} />
      <ClientComponent3 data={listMenuItems} />
    </header>
  );
};

export default Navbar;

the Navbar component look like this
Hunting waspOP
Oh I see what you mean, I should have the client component at a lower level while having the component that retrieves the data as a "wrapper"
Ok I will try this! Thank you
yes
does it work for you?
Hunting waspOP
So I tried, and this has brought me in a rabbit hole of problems :sweating:
So, first of all, I removed the use Client and inserted the API call directly into the component
what problem?
Hunting waspOP
I was doing something like this:
async function retrieveNavbarValues() { const response = await fetch(process.env.HOST_URL +/api/navbar) if (!response.ok) { console.log('Failed to retrieve navbar data') console.log(response) } return response.json() } const Navbar = async () => { const listMenuItems = await retrieveNavbarValues();

where HOST_URL contains "http://localhost:3000"
That says calling an internal component can have issues
well you cant fetch own api in server component
Hunting waspOP
At building time
So I just inserted the DB call inside the component
just move everything to the server component from api route
you don't need api route for rendering the page
Hunting waspOP
So I did this:
async function retrieveNavbarValues() { const allMenuItems: MenuItem[] = []; const session = await getServerSession(nextauthOptions); const client = await clientPromise; try { const role = session?.user?.role; const result: RoleDocument = await client .db(process.env.DB_NAME) .collection<RoleDocument>('roles') .findOne(({ role })) as RoleDocument; allMenuItems.push(...result?.menuItems); } catch (error) { console.error('Error fetching navbar from mongodb:', error); return new Response("Failed to fetch navbar structure", { status: 500 }) } return new Response(JSON.stringify(allMenuItems), { status: 200 }); } const Navbar = async () => { const response = await retrieveNavbarValues(); const listMenuItems: MenuItem[] = await response.json()
But now I am having a new error:
Module not found: Can't resolve 'fs'
This is the whole error:
Import trace for requested module: ./node_modules/@mapbox/node-pre-gyp/lib/ sync ^\.\/.*$ ./node_modules/@mapbox/node-pre-gyp/lib/node-pre-gyp.js ./node_modules/bcrypt/bcrypt.js ./app/api/auth/[...nextauth]/nextauthOptions.ts ./components/navigation/Navbar.tsx ./components/Index.tsx ./components/modal/aggiungiarticolo/AggiungiArticolo.tsx ⨯ ./node_modules/@mapbox/node-pre-gyp/lib/clean.js:8:0 Module not found: Can't resolve 'fs'
what version of next-auth are you using?
this is next-auth error
Hunting waspOP
I am using App router node version 20 and next-auth
"next-auth": "^4.24.5",
this are my nextauthOptions config:
// lib/nextauthOptions.ts import CredentialsProvider from "next-auth/providers/credentials"; import bcrypt from "bcrypt"; import { AuthOptions } from "next-auth"; import clientPromise from "@/lib/mongodb"; export const nextauthOptions: AuthOptions = { providers: [ CredentialsProvider({ id: "credentials", credentials: { username: { label: "Username", type: "text", }, password: { label: "Password", type: "password", }, }, async authorize(credentials) { const client = await clientPromise; const usersCollection = client .db(process.env.DB_NAME) .collection("users"); const username = credentials?.username.toLowerCase(); const user = await usersCollection.findOne({ username }); if (!user) { throw new Error("User does not exist."); } const passwordIsValid = await bcrypt.compare( credentials?.password!, user.password ); if (!passwordIsValid) { throw new Error("Invalid credentials"); } return { id: user._id.toString(), ...user, } as any; }, }), ], session: { strategy: "jwt", }, callbacks: { jwt({ token, user }) { if(user){ token.username = user.username token.role = user.role } return token }, session({ session, token }) { session.user.username = token.username session.user.role = token.role return session } }, };
try import { unstable_getServerSession } from "next-auth/next";
const session = await unstable_getServerSession();
and the retrieveNavbarValues function just return the result, don't need to return new Response
Hunting waspOP
Still have an error
@Ray ` const session = await unstable_getServerSession();`
try pass your authOption to it
the doc said unstable_getServerSession was renamed to getServerSession
see if you can import it? import { getServerSession } from "next-auth/next"
import { getServerSession } from "next-auth/next"
import { authOptions } from "pages/api/auth/[...nextauth]"

export default async function Page() {
  const session = await getServerSession(authOptions)
  return <pre>{JSON.stringify(session, null, 2)}</pre>
}
Hunting waspOP
it's says to me it's deprecated and to use the one I am using
will still try
same as before
Not it's algo giving me an error in the layout component in wich the NavBar component it's used
what verson of @types/react ?
Hunting waspOP
"@types/react": "^18.2.39",
But this has become to messy, I am looking now at a new way to just make the layout wait until the navbar has fetched its data
I don't know if It can be part of the problem, but here it's the project structure, navbar isn't inside the app folder but in the components one
what is the typescript version?
Hunting waspOP
"typescript": "^5.3.2",
hmm can you show the code in navbar?
Hunting waspOP
import Link from 'next/link' import DropdownMenu from '../dropdown/DropdownMenu'; // import { useEffect, useState } from 'react'; import { MenuItem, RoleDocument } from '@/types'; import { getServerSession } from 'next-auth'; import clientPromise from '@/lib/mongodb'; import { nextauthOptions } from '@/app/api/auth/[...nextauth]/nextauthOptions'; async function retrieveNavbarValues() { const allMenuItems: MenuItem[] = []; const session = await getServerSession(nextauthOptions); const client = await clientPromise; try { const role = session?.user?.role; const result: RoleDocument = await client .db(process.env.DB_NAME) .collection<RoleDocument>('roles') .findOne(({ role })) as RoleDocument; allMenuItems.push(...result?.menuItems); } catch (error) { console.error('Error fetching navbar from mongodb:', error); return allMenuItems; } return allMenuItems; } const Navbar = async () => { const listMenuItems = await retrieveNavbarValues(); // const [allMenuItems, setAllMenuItems] = useState<MenuItem[]>([]); // useEffect(() => { // const fetchNavbar = async () => { // try { // const response = await fetch(/api/navbar) // const data = await response.json(); // setAllMenuItems(data) // } catch (error) { // console.error('Error fetching data:', error); // } // } // fetchNavbar(); // }, []); return ( <header className="flex gap-10 items-center bg-textColor py-4 px-2"> <div className="flex gap-8 items-center text-white m-2.5"> {listMenuItems.map((item: MenuItem) => { return typeof item.children !== "undefined" ? ( <DropdownMenu item={item} /> ) : ( <Link className="" href={item?.route || ""}> {item.title} </Link> ); })} </div> </header> ); } export default Navbar
And this is the code of the root layout.tsx that imports the Navbar:
import "@/styles/globals.css"; import type { Metadata } from 'next' import { Montserrat } from 'next/font/google' import { Sidebar, Navbar, Provider } from '@/components/Index' import { getServerSession } from "next-auth/next"; import { redirect } from "next/navigation"; import { nextauthOptions } from './api/auth/[...nextauth]/nextauthOptions'; const montserrat = Montserrat({ subsets: ['latin'] }); export const metadata: Metadata = { title: '', description: '', } export default async function RootLayout({ children, }: { children: React.ReactNode }) { const session = await getServerSession(nextauthOptions); if (!session?.user) { const url = new URL("/api/auth/signin", "http://localhost:3000"); url.searchParams.append("callbackUrl", "/"); redirect(url.toString()); } return ( <html lang="it"> <body className={montserrat.className}> <Provider> <Sidebar /> <main className="h-screen main text-textColor m-0"> <Navbar /> {children} </main> </Provider> </body> </html> ) }
Async Server Component TypeScript Error
To use an async Server Component with TypeScript, ensure you are using TypeScript 5.1.3 or higher and @types/react 18.2.8 or higher.

If you are using an older version of TypeScript, you may see a 'Promise<Element>' is not a valid JSX element type error. Updating to the latest version of TypeScript and @types/react should resolve this issue.
well look like no problem with the version you using but try install typescript@latest @types/react@latest
Hunting waspOP
Ok I updated both but still have the issue :/, also tried deleting the node_modules and installing all again, I am using npm, but the issue it's still present
cmd+shift+p > Select typescript verion > use workspace version
Hunting waspOP
thanks! Tought it was automathic, solved the issue in vscode!
I still have the main issue:
do you have middleware.ts?
Hunting waspOP
This error it's new actually, I was trying to find a way to resolve the "Module not found: Can't resolve 'fs'"
this error is from next-auth
if you comment out getServerSession, the error should be gone
Hunting waspOP
ok let me try
are you on window and using bcrypt?
Hunting waspOP
I am using linux
I tried remoing the session from next-auth but I still have the error, even tho it's a different one:
Import trace for requested module: ./node_modules/mongodb/lib/client-side-encryption/auto_encrypter.js ./node_modules/mongodb/lib/index.js ./lib/mongodb.ts ./components/navigation/Navbar.tsx ./components/Index.tsx ./components/modal/aggiungiarticolo/AggiungiArticolo.tsx ⨯ ./node_modules/mongodb/lib/client-side-encryption/mongocryptd_manager.js:34:24 Module not found: Can't resolve 'child_process'
hmm should be related to mongodb client
what client you use?
Hunting waspOP
The one described in the documentation
// lib/mongodb.ts import { MongoClient, MongoClientOptions } from "mongodb"; if (!process.env.MONGODB_URI) { throw new Error('Invalid/Missing environment variable: "MONGODB_URI"'); } const IS_DEVELOPMENT = process.env.NODE_ENV === "development"; const uri = process.env.MONGODB_URI; const options: MongoClientOptions = {}; let client; let clientPromise: Promise<MongoClient>; if (IS_DEVELOPMENT) { // In development mode, use a global variable so that the value // is preserved across module reloads caused by HMR (Hot Module Replacement). let globalWithMongo = global as typeof globalThis & { _mongoClientPromise?: Promise<MongoClient>; }; if (!globalWithMongo._mongoClientPromise) { client = new MongoClient(uri, options); globalWithMongo._mongoClientPromise = client.connect(); } clientPromise = globalWithMongo._mongoClientPromise; } else { // In production mode, it's best to not use a global variable. client = new MongoClient(uri, options); clientPromise = client.connect(); } // Export a module-scoped MongoClient promise. By doing this in a // separate module, the client can be shared across functions. export default clientPromise;
"mongodb": "^6.3.0",
So it seems that I can't use the mongodb client from a component?
/** @type {import('next').NextConfig} */
const nextConfig = {
    experimental: {
        serverComponentsExternalPackages:['mongodb']
    }, 
}

module.exports = nextConfig
add this to next.config.ts
Hunting waspOP
Tried, still having the same issue
I just tried to init a new project using mongodb without problem
Hunting waspOP
what OS are you on?
mac
Hunting waspOP
I see. I don't really know what isnt' working right now, but I have other things to keep working on, so I will probably come back later on this problem
thanks anyways
hmm try init a clean project and see if mongodb work on your side?