Skip to content

Nr 04 Pages Vs Nested Routes

DodaTech 5 min read

title: "Next.js vs Remix — Pages vs Nested Routes Architecture" description: "Compare Next.js page-based routing with Remix nested routing architecture, including layout inheritance, parallel data loading, and route composition." weight: 14 date: 2026-06-28 lastmod: 2026-06-28 tags: [frameworks, react]

Next.js and Remix take different approaches to route composition. Next.js uses page-based routing with optional layouts. Remix uses nested routes with automatic layout inheritance.

What You'll Learn

You will understand the difference between page routing and nested routing, how each affects data loading, layout inheritance, and route behavior.

Why It Matters

Route architecture affects how data is loaded, how layouts render, and how navigation feels. Nested routes in Remix provide automatic parallel data loading that Next.js achieves manually.

Real-World Use

DodaTech's internal CRM uses Remix nested routes so the customer list loads once in the parent route, and each customer detail page loads its own data in a child route without refetching the list.

flowchart TD
    subgraph Next[Next.js Pages]
        A[/products] --> B[Page loads all data]
        A --> C[/products/:id]
        C --> D[Page loads its own data]
    end
    subgraph Remix[Remix Nested]
        E[/products] --> F[Parent loader: list data]
        E --> G[/products/:id]
        F --> H[Parent layout persists]
        G --> I[Child loader: detail data]
    end
    style Next fill:#121212,color:#fff
    style Remix fill:#1a1a2e,color:#fff

Next.js Page-Based Routing

In Next.js, each page is independent. When navigating from /products to /products/123, the entire page re-renders, fetching all data again.

// app/products/page.js — loads everything
export default async function ProductsPage() {
  const products = await db.products.findAll();
  return <ProductGrid products={products} />;
}

// app/products/[id]/page.js — loads everything again
export default async function ProductPage({ params }) {
  const product = await db.products.findById(params.id);
  return <ProductDetail product={product} />;
}

Expected output: Navigating from the product list to a product detail re-renders the whole page. The product list data is fetched again if you navigate back.

Remix Nested Routes

In Remix, each route segment has its own loader. Parent route data persists when navigating between child routes.

// app/routes/products.jsx
export async function loader() {
  return db.products.findAll();
}

export default function ProductsLayout({ children }) {
  const products = useLoaderData();
  return (
    <div style={{ display: 'flex' }}>
      <ProductSidebar products={products} />
      <main>{children}</main>
    </div>
  );
}

// app/routes/products.$id.jsx
export async function loader({ params }) {
  return db.products.findById(params.id);
}

export default function ProductDetail() {
  const product = useLoaderData();
  return <ProductView product={product} />;
}

Expected output: Navigating between product details updates only the right panel. The sidebar with the product list persists without refetching.

Parallel Data Loading in Remix

Remix loads data for all matching route segments in parallel. The parent loader and child loader run simultaneously.

// app/routes/users.jsx
export async function loader() {
  // Runs in parallel with child loader
  return db.users.findAll();
}

// app/routes/users.$id.jsx
export async function loader({ params }) {
  // Runs in parallel with parent loader
  return db.users.findById(params.id);
}

// app/routes/users.$id.settings.jsx
export async function loader({ params }) {
  // Runs in parallel with both parent loaders
  return db.settings.findByUserId(params.id);
}

Expected output: When visiting /users/123/settings, all three loaders run simultaneously. Total load time is the slowest single loader, not the sum of all three.

Layout Persistence Across Navigation

Remix preserves parent layouts during child navigation. Only the changed segment re-renders.

Next.js can achieve similar behavior using layout.js, but each page re-fetches its data unless explicitly cached.

Route Composition Patterns

Remix routes compose naturally. Child routes receive parent data through URL params and search params.

Next.js routes are more independent. Sharing data between parent and child routes requires lifting state to layouts or using context.

Common Mistakes

  1. Not leveraging Remix parallel loading: If you have three levels of nested routes, all data loads in parallel automatically. Do not create waterfalls by fetching in useEffect.

  2. Over-fetching in Next.js layouts: Layouts persist but do not prevent child pages from fetching their own data. Use caching to avoid redundant fetches.

  3. Creating unnecessarily deep nesting: Remix encourages nesting but deep nesting (5+ levels) can make the route structure hard to follow.

  4. Forgetting that Remix layouts re-render on parent data changes: If parent data changes, the layout re-renders but child data stays cached.

  5. Not using the Outlet component correctly: In Remix, child routes render where the parent puts . Forgetting it means children never render.

Practice Questions

  1. How does Remix handle data loading for nested routes?

Each route segment's loader runs in parallel. Parent and child loaders execute simultaneously when navigation occurs.

  1. What happens to parent layout data when navigating between child routes in Remix?

Parent layout data persists. Only the child route's data is fetched on navigation. The parent does not re-fetch.

  1. How does Next.js App Router achieve layout persistence?

Using layout.js files. The layout persists but does not automatically share data with child pages.

  1. What is the purpose of the Outlet component in Remix?

Outlet renders the child route component. It is placed in the parent layout where children should appear.

  1. How do you share data between parent and child routes in Next.js?

Use React Context, pass data through props from the layout, or use a shared data fetching utility with caching.

Challenge

Build a customer management interface with a list sidebar and detail view. Implement it in both frameworks and compare the data loading behavior when navigating between customers.

Frequently Asked Questions

Can Next.js App Router load data in parallel across routes?

Only within the same page using Suspense and Promise.all. Next.js does not automatically parallelize data loading across parent and child routes.

Does Remix support layout-level caching?

Yes. Parent loader data is cached in the browser during the session. It is not re-fetched unless the parent route's data is explicitly invalidated.

How do I prevent layout re-renders in Next.js?

Use React.memo on the layout component or pass data through a context provider to minimize re-renders.

Can I have multiple outlets in Remix?

No. Each route has one Outlet. For multiple sections, use parallel routes in Next.js or split into separate routes.

Does nested routing affect SEO?

No. Both frameworks render complete HTML. Search engines see the full content regardless of route structure.

Mini Project

Build a three-level nested interface (organization > projects > tasks) in both frameworks. Measure the data loading time for each level and compare the user experience of navigating between tasks.

What's Next

Continue to Data Loading Contrast for a detailed comparison of getServerSideProps vs Remix loaders.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro