---
title: "How to Fix Remix Dynamic Route Precedence: The Underscore Solution"
slug: "remix-dynamic-route-precedence-underscore-solution"
published: "2025-09-09"
updated: "2025-09-10"
validated: "2025-10-20"
categories:
  - "Sanity"
tags:
  - "Remix"
  - "routing"
  - "dynamic routes"
  - "file-based routing"
  - "route precedence"
  - "pathless layout routes"
  - "nested routes"
llm-intent: "reference"
audience-level: "intermediate"
framework-versions:
  - "remix@2.x"
  - "typescript@5.x"
  - "react@18.x"
  - "node@18.x"
status: "stable"
llm-purpose: "Fix Remix dynamic route precedence issues where parent routes override child routes. Use underscore notation (admin.reviews_.$id.tsx)"
llm-prereqs:
  - "Access to Remix"
  - "Access to TypeScript"
  - "Access to React"
llm-outputs:
  - "Completed outcome: Fix Remix dynamic route precedence issues where parent routes override child routes. Use underscore notation (admin.reviews_.$id.tsx)"
---

**Summary Triples**
- (Observed Behavior, occurs when, a parent route file and a dynamic child file share the same prefix (e.g., admin.reviews.tsx and admin.reviews.$id.tsx), causing the parent to match both /admin/reviews and /admin/reviews/:id)
- (Root Cause, is, Remix's file-based route matching ambiguity when sibling/child filenames share a prefix without a pathless layout separator)
- (Primary Fix, use, an underscore to create a pathless layout route between segments (example filename: admin.reviews_.$id.tsx))
- (How It Works, effect, the underscore marks a pathless layout/grouping segment so the dynamic segment ($id) is matched with correct precedence)
- (Concrete Rename Example, rename, admin.reviews.$id.tsx -> admin.reviews_.$id.tsx (or introduce admin.reviews_.tsx as a pathless layout and move child admin.reviews.$id.tsx under it))
- (Verification Step, verify, that loader of the dynamic child runs (add console.log or return unique content) and that visiting /admin/reviews/:id renders the child component)
- (Alternative Pattern, use, a dedicated pathless layout file (admin.reviews_.tsx) paired with admin.reviews.$id.tsx to separate index and dynamic children)
- (Risk, includes, TypeScript compilation errors if file references and imports are not updated after renames; use git to revert if needed)
- (When to Apply, apply, when a parent/index route unintentionally catches requests intended for a nested dynamic route)
- (Result, ensures, correct route precedence so /admin/reviews and /admin/reviews/:id render different components and loaders)

### {GOAL}
Fix Remix dynamic route precedence issues where parent routes override child routes. Use underscore notation (admin.reviews_.$id.tsx)

### {PREREQS}
- Access to Remix
- Access to TypeScript
- Access to React

### {STEPS}
1. Follow the detailed walkthrough in the article content below.

<!-- llm:goal="Fix Remix dynamic route precedence issues where parent routes override child routes. Use underscore notation (admin.reviews_.$id.tsx)" -->
<!-- llm:prereq="Access to Remix" -->
<!-- llm:prereq="Access to TypeScript" -->
<!-- llm:prereq="Access to React" -->
<!-- llm:output="Completed outcome: Fix Remix dynamic route precedence issues where parent routes override child routes. Use underscore notation (admin.reviews_.$id.tsx)" -->

# How to Fix Remix Dynamic Route Precedence: The Underscore Solution
> Fix Remix dynamic route precedence issues where parent routes override child routes. Use underscore notation (admin.reviews_.$id.tsx)
Matija Žiberna · 2025-09-09

I was building a reviews management system when I hit one of the most frustrating Remix routing bugs I've encountered. Both /admin/reviews/ and
  /admin/reviews/4e316b4e-4511-4418-a110-e15e31bfeae6 were rendering the exact same component - the general reviews list instead of individual review
  details. After hours of debugging route precedence, file naming conventions, and TypeScript compilation errors, I discovered the culprit: a critical but
  poorly documented Remix file-based routing behavior that breaks dynamic nested routes.

  If you're struggling with Remix dynamic routes not matching correctly, parent routes overriding child routes, or file-based routing conflicts between
  index and dynamic segments, this guide reveals the exact solution that finally made my nested routes work properly.

  The Problem: Remix Route Precedence Chaos

  Here's what should work in Remix but doesn't:

  app/routes/
  ├── admin.reviews.tsx          // Should handle /admin/reviews
  └── admin.reviews.$id.tsx      // Should handle /admin/reviews/{id}

  With this structure, visiting /admin/reviews/some-uuid should load the individual review component from admin.reviews.$id.tsx. Instead, Remix incorrectly
  routes both URLs to admin.reviews.tsx, showing the reviews list for both the index route and dynamic route.

  This creates a maddening debugging experience where your dynamic route loader never executes, your component never renders, and there's no error message
  explaining why. The route matching algorithm treats both paths as if they should resolve to the parent route, completely ignoring the dynamic segment.

  The Root Cause: Ambiguous Route Matching

  The issue stems from how Remix interprets file-based routing hierarchies. When you have admin.reviews.tsx and admin.reviews.$id.tsx in the same directory,
   Remix's route matcher cannot definitively distinguish between:

  - A request for the index route (/admin/reviews/)
  - A request for a dynamic child route (/admin/reviews/{id})

  This ambiguity causes the route matching algorithm to default to the parent route in both cases, effectively making your dynamic route unreachable. The
  problem is particularly insidious because both files exist, both compile successfully, and there are no runtime errors - the wrong component just silently
   renders.

  The Solution: Underscore Pathless Layout Routes

  The fix requires using Remix's underscore notation to create a "pathless layout route" that properly separates the dynamic child from its parent:

  app/routes/
  ├── admin.reviews.tsx          // Handles /admin/reviews
  └── admin.reviews_.$id.tsx     // Handles /admin/reviews/{id}

  The critical difference is the underscore before the dollar sign: admin.reviews_.$id.tsx. This tells Remix to create a child route that inherits the
  parent path (/admin/reviews/) but doesn't add an additional path segment - it treats the $id parameter as a direct child of the parent route.

  Here's the working implementation:

  // File: app/routes/admin.reviews_.$id.tsx
  import type { LoaderFunctionArgs } from "@remix-run/node";
  import { useLoaderData } from "@remix-run/react";

  export async function loader({ request, params }: LoaderFunctionArgs) {
    const { id } = params; // This now properly captures the dynamic segment

    // Your individual review loading logic
    const review = await getReview({ id });

    return { review };
  }

  export default function AdminReviewDetail() {
    const { review } = useLoaderData<typeof loader>();

    return (
      <div>
        <h1>Review Details</h1>
        <p>Review ID: {review.id}</p>
        {/* Individual review component */}
      </div>
    );
  }

  With this structure, Remix now correctly routes:
  - /admin/reviews/ → admin.reviews.tsx (index route)
  - /admin/reviews/4e316b4e-4511-4418-a110-e15e31bfeae6 → admin.reviews_.$id.tsx (dynamic child route)

  Why the Underscore Works: Pathless Layout Routes Explained

  The underscore in Remix routing creates what's called a "pathless layout route." This is a route that participates in the route hierarchy for component
  nesting and data loading but doesn't contribute its own path segment to the URL structure.

  Without the underscore, admin.reviews.$id.tsx attempts to create a route at the same hierarchical level as admin.reviews.tsx, causing the conflict. With
  the underscore, admin.reviews_.$id.tsx becomes a child route that inherits the parent's path (/admin/reviews/) and appends the dynamic segment directly.

  This distinction is crucial for complex nested routing scenarios where you need both index routes and dynamic child routes under the same path prefix. The
   underscore pattern ensures clean URL structures while maintaining proper component hierarchy and data loading behavior.

  Updating Your Route Links

  Don't forget to update any navigation links to use the correct URL structure:

  // File: app/routes/admin.reviews.tsx (general reviews list)
  <Button url={`/admin/reviews/${review.id}`}>
    View Details
  </Button>

  // File: app/routes/admin.stores.$id.tsx (store details page)
  <Button url={`/admin/reviews/${review.id}`}>
    View Review
  </Button>

  The URL structure remains clean and intuitive - only the file naming convention changes to resolve the routing conflict.

  The Broader Remix Routing Lesson

  This bug highlights a critical gap in Remix's file-based routing documentation. The framework's routing behavior isn't always intuitive, especially when
  dealing with nested dynamic routes, and the error messages don't help identify when routes are conflicting.

  Key takeaways for Remix routing:
  - Use underscores for pathless layout routes when you need dynamic children of index routes
  - File naming directly impacts route matching precedence
  - Silent routing conflicts can occur without error messages
  - Always test both parent and child route access during development

  The underscore pattern is essential knowledge for any Remix application with complex routing hierarchies. Understanding when and how to use pathless
  layout routes prevents hours of debugging mysterious routing behavior.

  Conclusion

  The Remix dynamic route precedence bug between admin.reviews.tsx and admin.reviews.$id.tsx is a perfect example of how file-based routing can create
  unexpected conflicts. The underscore notation in admin.reviews_.$id.tsx creates the pathless layout route structure needed to properly separate index
  routes from their dynamic children.

  You now know how to structure nested dynamic routes in Remix without losing your mind to silent routing conflicts. This pattern works for any scenario
  where you need both an index route and dynamic child routes under the same path prefix - user profiles, product pages, admin panels, or any hierarchical
  data structure.

  Let me know in the comments if you've encountered other Remix routing edge cases, and subscribe for more practical development guides that solve real
  debugging challenges.

  Thanks, Matija

## LLM Response Snippet
```json
{
  "goal": "Fix Remix dynamic route precedence issues where parent routes override child routes. Use underscore notation (admin.reviews_.$id.tsx)",
  "responses": [
    {
      "question": "What does the article \"How to Fix Remix Dynamic Route Precedence: The Underscore Solution\" cover?",
      "answer": "Fix Remix dynamic route precedence issues where parent routes override child routes. Use underscore notation (admin.reviews_.$id.tsx)"
    }
  ]
}
```