Advanced Route Configuration
In our previous lecture, we covered the basics of setting up routes with React Router. Now we'll explore more advanced route configuration options and patterns.
Dynamic Route Segments
React Router allows for highly customizable route paths with dynamic segments. Each dynamic segment is prefixed with a colon and becomes a parameter that can be accessed via useParams().
<Route path="/users/:userId" element={<UserProfile />} />
<Route path="/users/:userId/posts/:postId" element={<UserPost />} />
<Route path="/products/:category/:productId" element={<ProductDetail />} />
Accessing multiple parameters in a component:
function UserPost() {
const { userId, postId } = useParams();
return (
<div>
<h1>User {userId}'s Post {postId}</h1>
{/* Content here */}
</div>
);
}
Optional Parameters
To make a route parameter optional, you can use the ? modifier. This allows the route to match both with and without the parameter.
// This route will match both "/users" and "/users/123"
<Route path="/users/:userId?" element={<Users />} />
function Users() {
const { userId } = useParams();
if (userId) {
return <UserProfile userId={userId} />;
} else {
return <UsersList />;
}
}
Wildcard Routes and Splats
React Router has special syntax for creating wildcard routes that can match any pattern. The * (splat) parameter captures all remaining portions of the URL.
// Match any route that starts with /files/
<Route path="/files/*" element={<FileViewer />} />
function FileViewer() {
// Get the portion of the URL that matched the splat
const { '*': filePath } = useParams();
return (
<div>
<h1>File Viewer</h1>
<p>Viewing file: {filePath}</p>
{/* Render file content based on filePath */}
</div>
);
}
// For URL /files/documents/report.pdf
// filePath would be "documents/report.pdf"
This is particularly useful for:
- File system or document viewers
- Wiki-like applications
- Handling deep, dynamic paths
- Implementing catch-all routes
Real-World Example: Document Viewer
Here's how you might implement a document viewer with nested folders:
// Route definition
<Route path="/docs/*" element={<DocumentViewer />} />
// DocumentViewer component
function DocumentViewer() {
const { '*': docPath } = useParams();
const [document, setDocument] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function fetchDocument() {
try {
setLoading(true);
// API call to fetch the document
const response = await fetch(`/api/documents/${docPath}`);
if (!response.ok) {
throw new Error('Document not found');
}
const data = await response.json();
setDocument(data);
setError(null);
} catch (err) {
setError(err.message);
setDocument(null);
} finally {
setLoading(false);
}
}
fetchDocument();
}, [docPath]);
// Render loading state
if (loading) {
return <div>Loading document...</div>;
}
// Render error state
if (error) {
return <div>Error: {error}</div>;
}
// Render document
return (
<div className="document-viewer">
<div className="document-path">
<strong>Path:</strong> /docs/{docPath}
</div>
<h1>{document.title}</h1>
{document.type === 'folder' ? (
<div className="folder-contents">
<h2>Folder Contents</h2>
<ul>
{document.contents.map(item => (
<li key={item.name}>
<Link to={`/docs/${docPath}/${item.name}`}>
{item.name} ({item.type})
</Link>
</li>
))}
</ul>
</div>
) : (
<div className="document-content">
<div dangerouslySetInnerHTML={{ __html: document.content }} />
</div>
)}
</div>
);
}
This component would handle URLs like:
/docs/development(a folder)/docs/development/react(a subfolder)/docs/development/react/routing.md(a document)
All using a single route definition and component!
Route Priority and Order
The order of routes in React Router matters. Routes are matched from top to bottom, and the first match is rendered.
It's important to order your routes carefully, especially when you have:
- Static routes that could overlap with dynamic ones
- Multiple dynamic routes that could match the same URL
- Wildcard routes that would match everything
<Routes>
{/* Specific static routes should come first */}
<Route path="/products/new" element={<NewProduct />} />
<Route path="/products/special-offers" element={<SpecialOffers />} />
{/* Then dynamic routes */}
<Route path="/products/:productId" element={<ProductDetail />} />
{/* Finally, catch-all or wildcard routes */}
<Route path="/products/*" element={<ProductCatchAll />} />
</Routes>
With this ordering:
/products/newmatches the first route/products/123matches the dynamic route/products/anything/elsematches the wildcard route
If we reversed the order, the wildcard route would match everything, and the specific routes would never be reached!
Common Mistake: Route Ordering
One of the most common mistakes with React Router is improper route ordering. Consider this example:
// Incorrect ordering
<Routes>
<Route path="/products/*" element={<ProductCatchAll />} />
<Route path="/products/:productId" element={<ProductDetail />} />
<Route path="/products/new" element={<NewProduct />} />
</Routes>
With this incorrect ordering:
/products/newwould match the wildcard route (not the specific "new" route)/products/123would also match the wildcard route (not the dynamic route)
Always order routes from most specific to least specific (with wildcards last)!
Advanced Parameter Handling
Beyond simple path parameters, React Router provides ways to handle more complex parameter scenarios.
Custom Parameter Parsing
By default, URL parameters are parsed as strings. However, you can implement custom parsing for parameters, such as converting a string ID to a number:
function ProductDetail() {
const { productId } = useParams();
// Convert string ID to number
const id = parseInt(productId, 10);
// Check for invalid IDs
if (isNaN(id)) {
return <div>Invalid product ID</div>;
}
// Rest of the component...
}
Parameter Validation
It's a good practice to validate parameters to ensure they match expected formats:
function UserProfile() {
const { userId } = useParams();
const navigate = useNavigate();
// Validate UUID format
const isValidUUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(userId);
useEffect(() => {
// Redirect to 404 page if invalid UUID
if (!isValidUUID) {
navigate('/not-found', { replace: true });
}
}, [userId, isValidUUID, navigate]);
if (!isValidUUID) {
return null; // Will redirect
}
// Rest of the component...
}
Multiple Parameter Formats
Sometimes you need to support multiple URL formats for the same resource. You can handle this with separate routes pointing to the same component:
// Support both numeric IDs and slugs
<Route path="/products/:productId(\d+)" element={<ProductDetail />} />
<Route path="/products/:productSlug" element={<ProductDetail />} />
function ProductDetail() {
const params = useParams();
const productId = params.productId;
const productSlug = params.productSlug;
// Determine which parameter was used
const isIdRoute = !!productId;
useEffect(() => {
if (isIdRoute) {
// Fetch by ID
fetchProductById(productId);
} else {
// Fetch by slug
fetchProductBySlug(productSlug);
}
}, [isIdRoute, productId, productSlug]);
// Rest of the component...
}
Real-World Example: E-commerce Product URLs
Many e-commerce sites support multiple URL formats for products to improve SEO and user experience:
// in your routes config
<Routes>
{/* URL with category, product name, and ID for SEO */}
<Route
path="/shop/:category/:productName-:productId"
element={<ProductDetail />}
/>
{/* Direct ID access for sharing and compatibility */}
<Route
path="/products/:productId"
element={<ProductDetail />}
/>
</Routes>
// ProductDetail.js
function ProductDetail() {
const params = useParams();
const {
productId,
category,
productName
} = params;
const [product, setProduct] = useState(null);
const [loading, setLoading] = useState(true);
const navigate = useNavigate();
useEffect(() => {
async function fetchProduct() {
setLoading(true);
try {
const response = await fetch(`/api/products/${productId}`);
const data = await response.json();
setProduct(data);
// If we accessed via the direct route and product exists,
// redirect to the SEO-friendly URL
if (!category && !productName && data) {
const slug = data.name
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-');
navigate(
`/shop/${data.category}/${slug}-${data.id}`,
{ replace: true }
);
}
} catch (error) {
console.error('Error fetching product:', error);
} finally {
setLoading(false);
}
}
fetchProduct();
}, [productId, category, productName, navigate]);
if (loading) {
return <div>Loading...</div>;
}
if (!product) {
return <div>Product not found</div>;
}
return (
<div>
{/* Product detail content */}
</div>
);
}
This approach supports both URLs:
/shop/electronics/samsung-galaxy-s21-123(SEO-friendly)/products/123(direct access)
And automatically redirects from the direct URL to the SEO-friendly one when possible.
Route Objects and Configuration
For larger applications, defining routes directly in JSX can become unwieldy. React Router allows you to define routes as JavaScript objects, which can be more maintainable and enables dynamic route generation.
Creating Route Objects
You can define your routes as JavaScript objects and then use the useRoutes hook or the createBrowserRouter function:
// routes.js
import Home from './pages/Home';
import About from './pages/About';
import Products from './pages/Products';
import ProductDetail from './pages/ProductDetail';
import NotFound from './pages/NotFound';
const routes = [
{
path: '/',
element: <Home />
},
{
path: '/about',
element: <About />
},
{
path: '/products',
element: <Products />
},
{
path: '/products/:productId',
element: <ProductDetail />
},
{
path: '*',
element: <NotFound />
}
];
export default routes;
// App.js with useRoutes
import { BrowserRouter, useRoutes } from 'react-router-dom';
import routes from './routes';
function RouteRenderer() {
// useRoutes takes an array of route objects and returns a React element
return useRoutes(routes);
}
function App() {
return (
<BrowserRouter>
<div>
<Navbar />
<main>
<RouteRenderer />
</main>
</div>
</BrowserRouter>
);
}
Dynamic Route Generation
Using JavaScript objects for routes allows you to generate routes dynamically based on data:
// Dynamically generate routes for each product category
const categories = ['electronics', 'clothing', 'books', 'home', 'toys'];
const categoryRoutes = categories.map(category => ({
path: `/category/${category}`,
element: <CategoryPage category={category} />
}));
const routes = [
{ path: '/', element: <Home /> },
{ path: '/products', element: <Products /> },
// Spread the category routes
...categoryRoutes,
{ path: '*', element: <NotFound /> }
];
Route Configuration with Data Loading
In React Router v6.4+, you can use the Data API with createBrowserRouter to specify data loading logic for each route:
// routes.js
import { createBrowserRouter } from 'react-router-dom';
// Define loaders and actions for routes
async function productsLoader() {
const response = await fetch('/api/products');
if (!response.ok) {
throw new Error('Failed to load products');
}
return await response.json();
}
async function productLoader({ params }) {
const response = await fetch(`/api/products/${params.productId}`);
if (!response.ok) {
throw new Error('Failed to load product');
}
return await response.json();
}
async function submitContactForm({ request }) {
const formData = await request.formData();
const response = await fetch('/api/contact', {
method: 'POST',
body: formData
});
if (!response.ok) {
throw new Error('Failed to submit form');
}
return await response.json();
}
// Create the router with data loading
const router = createBrowserRouter([
{
path: '/',
element: <Layout />,
children: [
{
path: '/',
element: <Home />
},
{
path: '/products',
element: <Products />,
loader: productsLoader
},
{
path: '/products/:productId',
element: <ProductDetail />,
loader: productLoader
},
{
path: '/contact',
element: <Contact />,
action: submitContactForm
}
]
}
]);
export default router;
// main.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import { RouterProvider } from 'react-router-dom';
import router from './routes';
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<RouterProvider router={router} />
</React.StrictMode>
);
In the components, you can access the loaded data using the useLoaderData hook:
import { useLoaderData } from 'react-router-dom';
function Products() {
// This data comes from the productsLoader
const products = useLoaderData();
return (
<div>
<h1>Products</h1>
<ul>
{products.map(product => (
<li key={product.id}>
<Link to={`/products/${product.id}`}>
{product.name}
</Link>
</li>
))}
</ul>
</div>
);
}
When to Use Route Objects vs. JSX Routes
Both approaches have their merits:
| JSX Routes | Route Objects |
|---|---|
| More declarative and React-like | Better for dynamic route generation |
| Easier to understand for React developers | Better for large applications with many routes |
| Routes are defined where they're used | Routes can be defined separately and imported |
| Good for smaller applications | Easier to manipulate programmatically |
Choose based on your application's size and complexity. For larger applications or when you need dynamic route generation, route objects typically work better.
Route Guards and Protected Routes
Many applications need to restrict access to certain routes based on authentication status or user permissions. This is commonly achieved with route guards.
Basic Authentication Guards
A common pattern is to create a ProtectedRoute component that checks if the user is authenticated before rendering the desired component:
// components/ProtectedRoute.js
import { Navigate, useLocation } from 'react-router-dom';
import { useAuth } from '../contexts/AuthContext';
function ProtectedRoute({ children }) {
const { user } = useAuth();
const location = useLocation();
if (!user) {
// Redirect to login page if not authenticated
// Save the current location to redirect back after login
return <Navigate to="/login" state={{ from: location }} replace />;
}
// User is authenticated, render the protected component
return children;
}
export default ProtectedRoute;
// Usage in routes
<Routes>
<Route path="/" element={<Home />} />
<Route path="/login" element={<Login />} />
<Route
path="/dashboard"
element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
}
/>
<Route
path="/profile"
element={
<ProtectedRoute>
<UserProfile />
</ProtectedRoute>
}
/>
</Routes>
Role-Based Route Guards
You can extend the protected route concept to include role-based access control:
// components/RoleRoute.js
import { Navigate, useLocation } from 'react-router-dom';
import { useAuth } from '../contexts/AuthContext';
function RoleRoute({ children, requiredRoles }) {
const { user } = useAuth();
const location = useLocation();
// Check if user exists
if (!user) {
return <Navigate to="/login" state={{ from: location }} replace />;
}
// Check if user has any of the required roles
const hasRequiredRole = requiredRoles.some(role =>
user.roles.includes(role)
);
if (!hasRequiredRole) {
// User doesn't have the required role, redirect to unauthorized page
return <Navigate to="/unauthorized" replace />;
}
// User has a required role, render the protected component
return children;
}
export default RoleRoute;
// Usage in routes
<Routes>
<Route path="/" element={<Home />} />
<Route path="/login" element={<Login />} />
<Route path="/unauthorized" element={<Unauthorized />} />
{/* User-accessible routes */}
<Route
path="/dashboard"
element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
}
/>
{/* Admin-only routes */}
<Route
path="/admin"
element={
<RoleRoute requiredRoles={['admin']}>
<AdminDashboard />
</RoleRoute>
}
/>
{/* Multiple role access */}
<Route
path="/reports"
element={
<RoleRoute requiredRoles={['admin', 'analyst']}>
<Reports />
</RoleRoute>
}
/>
</Routes>
Using Auth Guards with Route Objects
If you're using route objects, you can implement protection with a higher-order component or wrapper function:
// Higher-order component to protect a route
function withAuth(Component) {
return function ProtectedComponent(props) {
const { user } = useAuth();
const location = useLocation();
if (!user) {
return <Navigate to="/login" state={{ from: location }} replace />;
}
return <Component {...props} />;
};
}
// Routes with protection
const routes = [
{ path: '/', element: <Home /> },
{ path: '/login', element: <Login /> },
{ path: '/dashboard', element: withAuth(Dashboard) },
{ path: '/profile', element: withAuth(UserProfile) }
];
Real-World Example: Multi-Level Auth with Layout Routes
In a real application, you might want different layouts for public vs. authenticated sections:
// Route configuration
import { createBrowserRouter, Navigate, Outlet } from 'react-router-dom';
import { useAuth } from './contexts/AuthContext';
// Layout components
function PublicLayout() {
return (
<div>
<PublicHeader />
<Outlet /> {/* Child routes render here */}
<PublicFooter />
</div>
);
}
function AuthLayout() {
const { user } = useAuth();
// Redirect to login if not authenticated
if (!user) {
return <Navigate to="/login" replace />;
}
return (
<div>
<AuthHeader user={user} />
<Sidebar />
<main>
<Outlet /> {/* Child routes render here */}
</main>
</div>
);
}
function AdminLayout() {
const { user } = useAuth();
// Check for admin role
if (!user || !user.roles.includes('admin')) {
return <Navigate to="/unauthorized" replace />;
}
return (
<div>
<AdminHeader user={user} />
<AdminSidebar />
<main>
<Outlet /> {/* Child routes render here */}
</main>
</div>
);
}
// Create router with nested layouts
const router = createBrowserRouter([
// Public routes
{
element: <PublicLayout />,
children: [
{ path: '/', element: <Home /> },
{ path: '/login', element: <Login /> },
{ path: '/register', element: <Register /> },
{ path: '/about', element: <About /> },
{ path: '/unauthorized', element: <Unauthorized /> }
]
},
// Authenticated user routes
{
element: <AuthLayout />,
children: [
{ path: '/dashboard', element: <Dashboard /> },
{ path: '/profile', element: <Profile /> },
{ path: '/settings', element: <Settings /> },
{ path: '/orders', element: <Orders /> }
]
},
// Admin routes
{
element: <AdminLayout />,
children: [
{ path: '/admin', element: <AdminDashboard /> },
{ path: '/admin/users', element: <UserManagement /> },
{ path: '/admin/products', element: <ProductManagement /> },
{ path: '/admin/settings', element: <AdminSettings /> }
]
},
// Catch-all route
{ path: '*', element: <NotFound /> }
]);
export default router;
This approach provides:
- Different layouts for different sections (public, user, admin)
- Protection at the layout level, so all child routes inherit it
- Clean separation of concerns
- Consistent UI for each section
Route Metadata and Custom Route Definitions
When using route objects, you can attach custom metadata to routes for various purposes, such as page titles, breadcrumb generation, or access control.
// routes.js with metadata
const routes = [
{
path: '/',
element: <Home />,
meta: {
title: 'Home',
requiresAuth: false,
breadcrumb: 'Home'
}
},
{
path: '/products',
element: <Products />,
meta: {
title: 'Products',
requiresAuth: false,
breadcrumb: 'Products'
}
},
{
path: '/products/:productId',
element: <ProductDetail />,
meta: {
title: 'Product Details',
requiresAuth: false,
breadcrumb: 'Product Details'
}
},
{
path: '/dashboard',
element: <Dashboard />,
meta: {
title: 'Dashboard',
requiresAuth: true,
breadcrumb: 'Dashboard'
}
}
];
// useRoutes doesn't natively use meta, so we need a wrapper
function RoutesWithMeta() {
const routeElements = useRoutes(routes);
return routeElements;
}
// PageTitle component to use route metadata
function PageTitle() {
const location = useLocation();
const currentRoute = routes.find(route =>
matchPath(route.path, location.pathname)
);
useEffect(() => {
if (currentRoute?.meta?.title) {
document.title = `${currentRoute.meta.title} - My App`;
} else {
document.title = 'My App';
}
}, [currentRoute]);
return null;
}
// Breadcrumbs component using route metadata
function Breadcrumbs() {
const location = useLocation();
const currentRoute = routes.find(route =>
matchPath(route.path, location.pathname)
);
if (!currentRoute?.meta?.breadcrumb) {
return null;
}
return (
<div className="breadcrumbs">
<Link to="/">Home</Link> /
<span>{currentRoute.meta.breadcrumb}</span>
</div>
);
}
// App component
function App() {
return (
<BrowserRouter>
<PageTitle />
<Breadcrumbs />
<RoutesWithMeta />
</BrowserRouter>
);
}
Custom Route Components
You can also create custom route components to handle specific behaviors:
// components/AnalyticsRoute.js
import { Route } from 'react-router-dom';
function AnalyticsRoute({ element, trackingId, ...rest }) {
// Enhanced element that tracks page views
const enhancedElement = (
<PageViewTracker trackingId={trackingId}>
{element}
</PageViewTracker>
);
return <Route {...rest} element={enhancedElement} />;
}
// Usage
<Routes>
<AnalyticsRoute
path="/"
element={<Home />}
trackingId="home-page"
/>
<AnalyticsRoute
path="/products"
element={<Products />}
trackingId="products-page"
/>
</Routes>
Real-World Example: Complete Page Configuration
Here's how you might configure routes with comprehensive metadata:
// routes.js
const routes = [
{
path: '/',
element: <Home />,
meta: {
title: 'Home',
description: 'Welcome to our e-commerce store',
requiresAuth: false,
roles: [],
breadcrumb: 'Home',
analyticsId: 'home-page',
preload: true,
layout: 'standard'
}
},
{
path: '/products',
element: <Products />,
meta: {
title: 'Products',
description: 'Browse our product catalog',
requiresAuth: false,
roles: [],
breadcrumb: 'Products',
analyticsId: 'products-list',
preload: true,
layout: 'standard'
}
},
{
path: '/dashboard',
element: <Dashboard />,
meta: {
title: 'User Dashboard',
description: 'Manage your account and orders',
requiresAuth: true,
roles: ['user'],
breadcrumb: 'Dashboard',
analyticsId: 'user-dashboard',
preload: false,
layout: 'dashboard'
}
},
{
path: '/admin',
element: <AdminPanel />,
meta: {
title: 'Admin Panel',
description: 'Site administration',
requiresAuth: true,
roles: ['admin'],
breadcrumb: 'Admin',
analyticsId: 'admin-panel',
preload: false,
layout: 'admin'
},
// Nested routes
children: [
{
path: 'users',
element: <UserManagement />,
meta: {
title: 'User Management',
description: 'Manage user accounts',
requiresAuth: true,
roles: ['admin'],
breadcrumb: 'Users',
analyticsId: 'admin-users',
preload: false,
layout: 'admin'
}
}
]
}
];
// RouteRenderer that applies all the metadata behavior
function RouteRenderer() {
const { user } = useAuth();
const location = useLocation();
// Function to find the current route
const findActiveRoute = (pathname, routesList, parent = null) => {
for (const route of routesList) {
const pattern = parent
? `${parent.path}/${route.path}`.replace(/\/+/g, '/')
: route.path;
const match = matchPath(pattern, pathname);
if (match) {
return { route, match };
}
if (route.children) {
const childMatch = findActiveRoute(pathname, route.children, route);
if (childMatch) return childMatch;
}
}
return null;
};
const activeRoute = findActiveRoute(location.pathname, routes);
// Set document metadata
useEffect(() => {
if (activeRoute?.route?.meta) {
const { title, description } = activeRoute.route.meta;
if (title) {
document.title = `${title} - My App`;
}
// Update meta description
let metaDescription = document.querySelector('meta[name="description"]');
if (!metaDescription) {
metaDescription = document.createElement('meta');
metaDescription.name = 'description';
document.head.appendChild(metaDescription);
}
if (description) {
metaDescription.content = description;
}
}
}, [activeRoute]);
// Track page view
useEffect(() => {
if (activeRoute?.route?.meta?.analyticsId) {
analytics.trackPageView(activeRoute.route.meta.analyticsId);
}
}, [activeRoute]);
// Auth checking function
const checkAuth = (route) => {
if (!route.meta) return true;
if (route.meta.requiresAuth && !user) {
return false;
}
if (route.meta.roles && route.meta.roles.length > 0) {
if (!user) return false;
const hasRequiredRole = route.meta.roles.some(
role => user.roles.includes(role)
);
if (!hasRequiredRole) return false;
}
return true;
};
// Custom rendering function
const renderRoutes = (routesList, parent = null) => {
return routesList.map(route => {
const path = parent
? `${parent.path}/${route.path}`.replace(/\/+/g, '/')
: route.path;
const isAuthorized = checkAuth(route);
// Determine which layout to use
const LayoutComponent = layouts[route.meta?.layout || 'standard'] || Fragment;
// Apply authorization and layout
const wrappedElement = (
<LayoutComponent>
{isAuthorized ? route.element : <Navigate to="/login" replace />}
</LayoutComponent>
);
// Recursively handle nested routes
if (route.children) {
return (
<Route key={path} path={path} element={wrappedElement}>
{renderRoutes(route.children, route)}
</Route>
);
}
return (
<Route key={path} path={path} element={wrappedElement} />
);
});
};
return <Routes>{renderRoutes(routes)}</Routes>;
}
This advanced setup provides:
- SEO management (title, description)
- Authentication and authorization checks
- Analytics tracking
- Dynamic layouts based on route metadata
- Breadcrumb generation
- Support for nested routes
Practice Activities
Activity 1: Advanced Route Parameters
Create a blog application with the following routes:
- Home page that lists all blog posts
- Blog post detail page using a slug parameter (
/blog/my-first-post) - Support both slug and ID formats for posts (
/blog/123should also work) - Author page that shows all posts by an author (
/author/john-doe) - Category page that shows all posts in a category (
/category/technology)
Make sure to:
- Validate parameters and handle invalid values
- Implement proper route ordering
- Use dynamic segments effectively
Activity 2: Route Guards and Authentication
Extend your blog application with authentication and route protection:
- Add login and registration pages
- Create a user dashboard that's only accessible to logged-in users
- Add an admin section that's only accessible to users with the "admin" role
- Implement different layouts for public, user, and admin sections
- Add a profile page that shows the logged-in user's information
Implement route guards to protect the appropriate routes.
Activity 3: Route Configuration and Metadata
Refactor your blog application to use route objects and metadata:
- Convert all routes to use route objects instead of JSX
- Add metadata to each route (title, description, etc.)
- Create components that use this metadata (PageTitle, MetaTags, Breadcrumbs)
- Implement analytics tracking based on route metadata
- Create a route visualization component that shows the site hierarchy
Summary
- Advanced route configuration allows for complex URL patterns and dynamic segments
- Route ordering is crucial for correct route matching, with more specific routes coming first
- Route parameters can be validated, transformed, and used with multiple formats
- Route objects provide a programmatic way to define and manipulate routes
- Route guards protect routes based on authentication, roles, or other conditions
- Route metadata enables additional functionality like SEO management and analytics
- Layouts can be combined with routes for consistent UI sections