š” Fetching Data from MongoDB in Next.js 15 Without useEffect
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.
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
awaitinside 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! |