📡 Fetching Data from MongoDB in Next.js 15 Without useEffect

When working with React, you’re probably used to fetching data using useEffect()
and making an API call to your backend. But in Next.js 15, things get much more powerful—and simpler—thanks to server components.
In this blog post, we’ll show you how to:
Connect directly to a MongoDB database
Fetch data server-side (no
useEffect
, no frontend fetching)Use async/await in server components
Pass that data into your frontend UI
Let’s dive in!
⚙️ The Next.js 15 Superpower: Built-in Backend
In traditional React, frontend and backend are separate. You must:
Create a backend API
Use
useEffect()
in React to fetch dataDeal with loading states manually
But in Next.js, backend and frontend are merged. Every server component can directly access your database — securely and privately.
🧱 Setup: Creating a lib
Folder
To keep database logic separate from UI logic, we’ll create a special folder in our project root:
📁 lib/
└── 📄 meals.js
Here’s where we’ll write the MongoDB logic to fetch meals.
🔗 Connecting to MongoDB
Install the MongoDB Node.js driver:
npm install mongodb
Now, inside lib/meals.js
, add this code:
import { MongoClient } from 'mongodb';
const uri = process.env.MONGODB_URI;
const dbName = 'your-db-name';
let client;
let clientPromise;
if (!global._mongoClientPromise) {
client = new MongoClient(uri);
global._mongoClientPromise = client.connect();
}
clientPromise = global._mongoClientPromise;
export async function getMeals() {
const client = await clientPromise;
const db = client.db(dbName);
const meals = await db.collection('meals').find().toArray();
// Simulate delay for learning purpose
await new Promise((resolve) => setTimeout(resolve, 1000));
return meals;
}
✅ Why use global._mongoClientPromise
?
Because MongoDB connections are expensive. In development mode (where files reload frequently), this keeps the connection cached and reused.
📄 Fetching from a Server Component
Now, go to app/meals/page.js
and fetch your meals like this:
import MealsGrid from '@/components/meals/meals-grid';
import { getMeals } from '@/lib/meals';
export default async function MealsPage() {
const meals = await getMeals();
return (
<>
<header>...your header here...</header>
<main>
<MealsGrid meals={meals} />
</main>
</>
);
}
🧠 You might be surprised: we're using
await
inside a component!
That’s possible because this is a server component — it only runs on the server.
💬 Why Not useEffect?
You don’t need useEffect
because the page already runs on the server:
The server grabs meals from MongoDB
Then sends the HTML to the browser
The browser just displays it!
This approach is faster, SEO-friendly, and simpler.
🖼️ What About Images?
We store the path to each meal's image in MongoDB, e.g.:
{
"title": "Spaghetti",
"image": "spaghetti.jpg",
"summary": "Delicious Italian pasta",
"slug": "spaghetti"
}
These images live in /public/meals/
, so we render them like:
<Image
src={`/meals/${image}`}
alt={title}
fill
/>
⚠️ Why use fill
?
Because we don’t know the exact image dimensions at build time. The fill
prop makes the image stretch to the size of its parent container. It’s perfect for dynamic images.
✅ Final Recap
What You Did | How It Helps |
🔌 Connected to MongoDB using lib/meals.js | Centralizes DB logic |
🔄 Used await getMeals() in server component | No need for fetch or useEffect |
🖼️ Rendered images dynamically with <Image fill /> | Works great for unknown image sizes |
🧠 Learned about server components | Full-stack React power! |
Subscribe to my newsletter
Read articles from Muhammad Sufiyan directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Muhammad Sufiyan
Muhammad Sufiyan
As a former 3D Animator with more than 12 years of experience, I have always been fascinated by the intersection of technology and creativity. That's why I recently shifted my career towards MERN stack development and software engineering, where I have been serving since 2021. With my background in 3D animation, I bring a unique perspective to software development, combining creativity and technical expertise to build innovative and visually engaging applications. I have a passion for learning and staying up-to-date with the latest technologies and best practices, and I enjoy collaborating with cross-functional teams to solve complex problems and create seamless user experiences. In my current role as a MERN stack developer, I have been responsible for developing and implementing web applications using MongoDB, Express, React, and Node.js. I have also gained experience in Agile development methodologies, version control with Git, and cloud-based deployment using platforms like Heroku and AWS. I am committed to delivering high-quality work that meets the needs of both clients and end-users, and I am always seeking new challenges and opportunities to grow both personally and professionally.