Logo
EN

Remix Routes

Overview

The goal of this article is to understand in depth how the two types of routes work within remix.

Nested Routes

The concept of nested routes in web development frameworks allows you to create a hierarchical structure of routes. This is useful for organizing your application into a tree of routes.

Nested routes create a parent-child relationship between routes. A parent route can have one or more child routes nested within it.

When using nested routes, the parent route's component serves as a layout component. It provides a consistent structure that wraps around the child routes' components. This allows you to maintain common elements like headers, sidebars, or footers across related pages.

Child routes use relative paths to define their URLs. These paths are relative to the parent route's path. For example, if the parent route's path is /users, a child route with path /profile would result in the full URL /users/profile.

Parallel Loading

Parallel loading is a technique used in web applications to optimize the loading of resources, such as data and assets, by allowing multiple requests to be processed simultaneously.

This approach contrasts with sequential loading, where resources are fetched one after the other, leading to slower user experiences, especially when data dependencies are not interdependent.

On of the problems with parallel loading is when a page is rendered, the application may need to load various data and assets in a specific order.

Different components of a page may require data from various sources, which, if loaded sequentially, can delay the overall rendering of the page.

Components that are dependent on one another may cause a chain of requests, where the completion of one request is necessary before the next can begin.

This can lead to a poor user experience, especially if the initial requests take time to complete.

Parallel Loading in Remix

Remix uses its nested routing system to optimize load times through parallel loading.

When a URL matches multiple routes, Remix identifies all the relevant routes that need to be loaded. Each route can have its own data dependencies.

Instead of waiting for one route to finish loading before starting another, Remix sends requests for all matching routes concurrently. This means that all necessary data and assets can be fetched at the same time.

Modern browsers are designed to handle multiple concurrent requests efficiently. By utilizing this capability, Remix can significantly reduce the time it takes for a user to see the content they are requesting.

Remix's Conventional Route Configuration

By adhering to a specific folder structure and file naming conventions, Remix automatically associates files with routes, eliminating the need for manual configuration.

In Remix, when you creates a file within the app/routes folder, Remix automatically recognizes it as a route.

This convention streamlines the process of defining routes, mapping them to URLs, and rendering the corresponding components.

The Remix provide the following folder structure as an example:

app/
├── routes/
   ├── _index.tsx
   ├── about.tsx
   ├── concerts._index.tsx
   ├── concerts.$city.tsx
   ├── concerts.trending.tsx
   └── concerts.tsx
└── root.tsx

Based on the above folder structure, the following routes are created:

concerts._index.tsx vs concerts.tsx

You may ask yourself, why do we need app/routes/concerts._index.tsx and app/routes/concerts.tsx?

Imagine you are building a concert management application where users can view upcoming concerts, trending concerts, and details about specific concerts in different cities.

app/
├── routes/
   ├── concerts.tsx          # Parent route for concerts
   ├── concerts._index.tsx   # Index route for concerts
   ├── concerts.trending.tsx  # Route for trending concerts
   └── concerts.$city.tsx     # Dynamic route for concerts in a specific city
└── root.tsx                  # Main app layout

The app/routes/concerts.tsx file serves as the parent route for the entire concerts section. It provides a layout that will wrap around all child routes related to concerts.

Let’s say this file contains a navigation menu and a header for the concerts section. It might look like this:

// app/routes/concerts.tsx
import { Outlet } from '@remix-run/react';
 
export default function ConcertsLayout() {
  return (
    <div>
      <h1>Concerts</h1>
      <nav>
        <ul>
          <li><Link to="/concerts">Home</Link></li>
          <li><Link to="/concerts/trending">Trending</Link></li>
        </ul>
      </nav>
      <Outlet /> {/* This is where child routes will render */}
    </div>
  );
}

The app/routes/concerts._index.tsx file defines what users see when they navigate to the base URL of the concerts section (/concerts).

This is the default view for the concerts section, the <Outlet /> component will render this route by default.

When the user types /concerts in their browser or clicks on the "Home" link in the navigation menu, Remix matches this URL to both concerts.tsx and concerts._index.tsx

Inside concerts.tsx, the <Outlet /> component is called, which tells Remix to render the matched child route.

Since the user is at /concerts, concerts._index.tsx is rendered inside the Outlet, showing the list of upcoming concerts.

Concerts
Home | Trending
 
Upcoming Concerts
- Concert A - Date: January 1
- Concert B - Date: February 15
- Concert C - Date: March 20

Do concerts.trending.tsx also needs a concerts.trending._index.tsx ?

The short answer is depend. Depends on the specific requirements of your application and how you want to structure your routes.

If you want to route different components based in nested routes from the concerts.trending.tsx the answer is yes, you must define the <Outlet /> component in the concerts.trending.tsx file and their concerts.trending._index.tsx file.

If the answer is not, you'are done.

The _index.tsx route is mandatory for the parent route of the page only, if you want to create a nested route based on a child route, you ca go same. Like the following example:

// app/routes/concerts.trending.tsx
import { Outlet } from '@remix-run/react';
 
export default function TrendingConcerts() {
  return (
    <div>
      <h2>Trending Concerts</h2>
      <Outlet /> {/* Renders concerts.trending._index.tsx or any nested routes */}
    </div>
  );
}

Finally, the concerts.trending._index.tsx file is responsible for rendering the list of upcoming concerts.

// app/routes/concerts.trending._index.tsx
export default function TrendingConcertsIndex() {
  return (
    <div>
      <h3>Featured Trending Concerts</h3>
      <ul>
        <li>Concert X - Date: January 5</li>
        <li>Concert Y - Date: February 10</li>
      </ul>
    </div>
  );
}

You can see that the concerts.trending._index.tsx file is responsible for rendering the list of trending concerts.

Conventional Routes Folders

Is the second method to define routes in Remix. One of the recommended practices is to use folders within the app/routes directory, each containing a route.tsx file along with any related modules or assets. This approach offers several advantages, as outlined below.

Co-locates Modules

By placing all elements related to a specific route within a dedicated folder, you ensure that the logic, styles, and components are closely knit.

For example, if you have a route for concerts, you can include all concert-related files (like components, styles, and utility functions) in the same folder.

app/
├── routes/
│   ├── concerts/
│   │   ├── favorites-cookie.ts
│   │   └── route.tsx

This organization makes it easier to understand the functionality of a route at a glance.

Simplifies Imports

With related modules in one place, you can manage imports more efficiently.

For instance, if you have a component and its associated styles in the same folder, you can import them directly without navigating through multiple directories.

// Inside app/routes/concerts/route.tsx
import FavoritesCookie from './favorites-cookie';
import styles from './concerts.module.css';
 
export default function Concerts() {
  return (
    <div className={styles.concerts}>
      <FavoritesCookie />
      {/* Other components */}
    </div>
  );
}

I don't like this approach, I prefer alias imports.

Anatomy of Routes Folder Conventional

Let's say we have the following folder structure:

app/
├── routes/
│   ├── _index/
│   │   ├── signup-form.tsx     // Related to the index route
│   │   └── route.tsx            // Main index route
│   ├── about/
│   │   ├── header.tsx           // Header component for the about page
│   │   └── route.tsx            // Main about route
│   ├── concerts/
│   │   ├── favorites-cookie.ts   // Logic for handling favorites
│   │   └── route.tsx            // Main concerts route
│   ├── concerts.$city/
│   │   └── route.tsx            // Dynamic route for concerts in a specific city
│   ├── concerts._index/
│   │   ├── featured.tsx         // Featured concerts component
│   │   └── route.tsx            // Index route for concerts
│   └── concerts.trending/
│       ├── card.tsx             // Component for displaying concert cards
│       ├── route.tsx            // Main trending concerts route
│       └── sponsored.tsx         // Component for sponsored concerts
└── root.tsx                     // Main application layout

None of the files will be taken as a route using this folder structure, the folders defined inside the routes folder will be taken as routes.

The _index folder

This is the main index, an index route is the default route that renders when a user navigates to the base URL of that section without any additional path segments.

For example, if you have a route structure like /app, the index route would be responsible for rendering the content when a user visits /app.