📡 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 data

  • Deal 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 DidHow It Helps
🔌 Connected to MongoDB using lib/meals.jsCentralizes DB logic
🔄 Used await getMeals() in server componentNo need for fetch or useEffect
🖼️ Rendered images dynamically with <Image fill />Works great for unknown image sizes
🧠 Learned about server componentsFull-stack React power!

0
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.