Modern web applications are growing in sophistication, and so are their routing needs. Whether you are building a multi-page application (MPA) with Django, a modular microservices environment via Docker, or an interactive user interface with React.js, understanding and implementing nested routes and catch all routes is essential for robust, maintainable, and scalable systems. In this article, we'll break down these concepts, explain why they're crucial, and walk through their implementation with real code and practical use cases across popular frameworks.
In web applications, routing refers to the mechanism of mapping URLs (web addresses) to specific code that handles requests. Think of routes as the “roads” users travel as they explore your application. A well-designed routing system ensures that users reach the correct destination (web page, API action, component) regardless of complexity or nested structure.
/blog/2024/06/nested-routingAs applications scale, routes often become multi-leveled and need to handle “unknown” or dynamic paths. That’s where nested routes and catch all routes come in.
A nested route is a routing pattern where a route exists within the context of another route. In plain terms, these are "routes inside routes." It allows applications to represent parent-child relationships within the URL structure, mirroring the actual page or component hierarchy.
/dashboard/dashboard/users or /dashboard/settingsProperly used, nested routes help organize large applications, make URL structures predictable, and enable fine-grained access control and code splitting.
Suppose you are building a SaaS (Software as a Service) dashboard, with main sections like “Projects”, “Billing”, and “Team Settings”. Each section contains sub-pages:
/dashboard/projects (list projects)/dashboard/projects/:projectId (view/edit a specific project)/dashboard/settings/users (manage users)/dashboard/settings/billing (view billing info)
By nesting settings routes under dashboard, you make clear their relationship and simplify access control (e.g., only authenticated users can access any /dashboard/* route).
A catch all route (sometimes called a “wildcard route”) is a pattern that matches any path not previously handled by the routing system. Its main purpose is to gracefully handle requests for unknown URLs, which can happen if users follow outdated links, make typos, or access dynamic content not previously defined.
* or similar syntax to define these routes.
A robust application always has a catch all route as the last line of defense for unroutable URLs.
React.js, especially with React Router v6+, offers a powerful and flexible approach to nested and catch all routes via a declarative route hierarchy.
In React Router, you define parent and child routes via a nested tree. Each parent route renders an <Outlet/> component, which acts as a placeholder for matching child routes. This allows deeply nested route hierarchies to map directly to URL structure and UI layout.
import { BrowserRouter, Routes, Route, Outlet } from "react-router-dom";
function DashboardLayout() {
return (
<div>
<h2>Dashboard</h2>
<Outlet /> {/* Nested route content will render here */}
</div>
);
}
export default function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/dashboard" element={<DashboardLayout />}>
<Route path="projects" element={<ProjectsList />} />
<Route path="projects/:projectId" element={<ProjectDetail />} />
<Route path="settings" element={<Settings />}>
<Route path="users" element={<UsersSettings />} />
<Route path="billing" element={<BillingSettings />} />
</Route>
</Route>
{/* Catch all route */}
<Route path="*" element={<NotFound />} />
</Routes>
</BrowserRouter>
);
}
Notice:
path="projects/:projectId" represents a dynamic “projectId” parameter, e.g., /dashboard/projects/42path="*" matches all unmatched URLs and renders the NotFound componentFor large applications, catch all routes can be leveraged to lazy load pages or switch rendering strategies for dynamic resources. For example, in a React.js e-commerce app with user-generated store URLs, a catch all route can load the correct store if it exists, otherwise show a 404.
<Route path="/store/*" element={<StoreRouter />} />
// In StoreRouter.jsx
import { useParams } from "react-router-dom";
function StoreRouter() {
const { "*" : path } = useParams(); // get “rest” of URL
// Fetch store by path, render StorePage or NotFound
}
Django, the Python web framework, uses URLconf (URL configuration) to map URLs to view functions or classes. You define patterns using regular expressions or path converters, allowing for both nested and catch all patterns.
In Django, nesting isn't realized by "embedding" views, but by composing URL patterns. You build up modules of URLs using include to keep your code structured by feature.
# project/urls.py
from django.urls import path, include
urlpatterns = [
path('dashboard/', include('dashboard.urls')),
]
# dashboard/urls.py
from django.urls import path, include
urlpatterns = [
path('projects/', include('projects.urls')),
path('settings/', include('settings.urls')), # Nested modules
]
# settings/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('users/', views.users_list, name='users'),
path('billing/', views.billing, name='billing'),
]
This compositional approach mirrors nested routing by splitting configuration into logical modules, keeping the system maintainable and scalable.
Django processes URL patterns in order, using the first match. A catch all route can be defined to match any URL not processed above (commonly for custom 404s or CMS-style dynamic pages).
# At the bottom of your urls.py
from django.urls import re_path
from . import views
urlpatterns += [
re_path(r'^(?P<path>.*)$', views.catch_all), # Matches anything
]
Important: Place catch all routes last because Django stops at the first match. Use them for:
Routing in microservices, especially those orchestrated with Docker and managed by reverse proxies or API gateways (like NGINX, Traefik, or Kong), uses the same nested/catch-all approaches at the infrastructure level.
/api/users/ get routed to the user service, requests to /api/payments/ go to the payment service, etc.
# Example NGINX config for Docker-based microservices
location /api/users/ {
proxy_pass http://user_service:8000/;
}
location /api/payments/ {
proxy_pass http://payment_service:8000/;
}
# Catch all for unmatched API calls
location /api/ {
return 404 '{"error": "Not found"}';
add_header Content-Type application/json;
}
By combining these strategies with Docker orchestration, you ensure each service is isolated, its endpoints are easy to map, and the system scales horizontally while handling edge cases gracefully.
When designing route architectures, especially in large systems, you must consider:
import { BrowserRouter, Route, Routes } from "react-router-dom";
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="products" element={<ProductList />} />
<Route path="products/:slug" element={<ProductDetail />} />
<Route path="*" element={<Custom404 />} />
</Routes>
</BrowserRouter>
);
}
Here, all product detail lookups are neatly nested, and unmatched URLs display a custom error page.
urlpatterns = [
path('docs/', include('docs.urls')),
# Catch all for documentation slugs
re_path(r'^docs/(?P<path>.*)$', views.docs_catch_all),
]
This pattern supports any /docs/* content without needing to define each URL in advance, perfect for CMS-driven documentation.
location /api/inventory/ {
proxy_pass http://inventory_service:8000/;
}
location /api/ {
return 404 '{"error": "API endpoint not found"}';
}
Such config ensures new microservices can be added modularly, and that consumers receive clear feedback when routing fails.
Effective use of nested routes and catch all routes is foundational for system designers and architecture-focused developers. We've covered:
Whether you're expanding a React.js application, modularizing a Django project, or scaling API gateways in a Docker-based system, structuring your routes thoughtfully will future-proof your platform and deliver seamless user experiences. Your next steps could involve combining these routing strategies with advanced authentication, role-based access, and automated testing for full production-level readiness.
