Skip to content

Nr 05 Data Loading Contrast

DodaTech 5 min read

title: "Next.js vs Remix — Data Loading: getServerSideProps vs Loaders" description: "Compare data loading approaches in Next.js and Remix: getServerSideProps, Server Components, and Remix loaders with parallel execution and caching." weight: 15 date: 2026-06-28 lastmod: 2026-06-28 tags: [frameworks, react]

Data loading is where Next.js and Remix differ most significantly. Next.js offers multiple methods across two routers. Remix uses a single loader pattern.

What You'll Learn

You will understand the data loading patterns in both frameworks, how to fetch data, handle caching, and manage errors in each approach.

Why It Matters

Data loading affects page performance, user experience, and code organization. Choosing the right pattern is essential for building efficient applications.

Real-World Use

DodaZIP migrated from Next.js getServerSideProps to Remix loaders and found that parallel data loading reduced page load time by 30 percent for the admin dashboard.

flowchart LR
    subgraph Next[Next.js Data Loading]
        A1[getServerSideProps] --> B1[Single function per page]
        A2[Server Components] --> B2[async/await in component]
        A3[getStaticProps] --> B3[Build-time data]
    end
    subgraph Remix[Remix Data Loading]
        C1[Loader function] --> D1[Per-route data]
        D1 --> E1[Parallel across routes]
        D1 --> F1[Separated from component]
    end
    style Next fill:#121212,color:#fff
    style Remix fill:#1a1a2e,color:#fff

Next.js getServerSideProps (Pages Router)

In the Pages Router, data fetching is separate from the component via getServerSideProps.

export async function getServerSideProps({ params, req, res }) {
  const [user, posts] = await Promise.all([
    db.users.findById(params.id),
    db.posts.findByAuthor(params.id),
  ]);

  if (!user) {
    return { notFound: true };
  }

  return {
    props: { user, posts },
  };
}

export default function ProfilePage({ user, posts }) {
  return (
    <div>
      <h1>{user.name}</h1>
      <ul>{posts.map(p => <li key={p.id}>{p.title}</li>)}</ul>
    </div>
  );
}

Expected output: The server runs getServerSideProps on every request, fetches user and posts in parallel, and passes the data as props to the component.

Next.js Server Components (App Router)

In the App Router, data fetching is embedded directly in Server Components.

export default async function ProfilePage({ params }) {
  const [user, posts] = await Promise.all([
    db.users.findById(params.id),
    db.posts.findByAuthor(params.id),
  ]);

  if (!user) {
    notFound();
  }

  return (
    <div>
      <h1>{user.name}</h1>
      <ul>{posts.map(p => <li key={p.id}>{p.title}</li>)}</ul>
    </div>
  );
}

Expected output: The Server Component fetches data directly with async/await. The component is both the data fetcher and the renderer.

Remix Loaders

Remix uses route-level loaders that are separate from the component.

export async function loader({ params, request }) {
  const [user, posts] = await Promise.all([
    db.users.findById(params.id),
    db.posts.findByAuthor(params.id),
  ]);

  if (!user) {
    throw new Response(null, { status: 404, statusText: 'Not Found' });
  }

  return { user, posts };
}

export default function ProfilePage() {
  const { user, posts } = useLoaderData();
  return (
    <div>
      <h1>{user.name}</h1>
      <ul>{posts.map(p => <li key={p.id}>{p.title}</li>)}</ul>
    </div>
  );
}

Expected output: The loader runs on the server before the component renders. useLoaderData provides the data to the component. The loader can throw Response objects for error handling.

Caching in Each Approach

Next.js offers granular caching options: revalidate for ISR, cache: force-cache for static data, and cache: no-store for dynamic data.

Remix does not provide built-in caching. Data is fetched on every request. You implement caching at the CDN or database layer.

Error Handling

Next.js getServerSideProps returns { notFound: true } for 404s or throws errors for 500s. Server Components use notFound() and error boundaries.

Remix loaders throw Response objects for any HTTP status code. The framework catches these and renders the appropriate error boundary.

Common Mistakes

  1. Forgetting that Next.js loaders are per-page, not per-route-segment: In App Router, each page fetches its own data. Parent layouts do not automatically share data with child pages.

  2. Not leveraging parallel loading in either framework: Use Promise.all for independent data sources. Sequential awaits create waterfalls.

  3. Returning non-serializable data from loaders: Both frameworks require serializable return values. Convert dates, ObjectIds, and complex objects.

  4. Mixing getServerSideProps and Server Components: These are for different routers. Do not use getServerSideProps in the App Router or Server Components in the Pages Router.

  5. Not handling the loading state in Remix: Remix does not have a built-in loading state for individual loaders. Use useNavigation for global loading UI.

Practice Questions

  1. What is the equivalent of getServerSideProps in the App Router?

async Server Components. Data is fetched directly in the component with async/await.

  1. How does Remix handle 404s in loaders?

Throw a Response object with status 404. The framework catches it and renders the error boundary.

  1. Can Remix loaders return cached data?

Remix does not have built-in caching. Use CDN caching, database query caching, or implement in-memory caching.

  1. What hook accesses loader data in Remix components?

useLoaderData. It returns the data returned by the route's loader function.

  1. How do Next.js Server Components handle loading states?

They do not have loading states. Wrap them in Suspense boundaries and use loading.js for the route.

Challenge

Build a page that fetches user data, their order history, and product recommendations. Implement it in Next.js App Router (Server Components) and Remix (loaders). Compare the code structure.

Frequently Asked Questions

Can I use getServerSideProps in the App Router?

No. getServerSideProps is for the Pages Router. Use async Server Components in the App Router.

Do Remix loaders support TypeScript?

Yes. Loaders are typed. Use typeof loader to get the type for useLoaderData.

Can I call a loader from another loader?

No. Each route has its own loader. Extract shared logic into utility functions that both loaders can call.

How do I pass data from a Remix loader to nested routes?

Nested route loaders can read the parent route's data using useRouteLoaderData or by passing data through URL params.

Does Next.js cache getServerSideProps results?

No. getServerSideProps runs on every request. Use ISR or a CDN cache layer to cache results.

Mini Project

Create a blog with posts and comments. Implement the post page with its comments data loaded in parallel. Compare the implementation in Next.js App Router and Remix.

What's Next

Compare Mutations and Forms across the two frameworks, including API routes vs Remix actions.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro