Routing & Navigation
Configure routes and navigation in your EZ-Console application.
Overview
EZ-Console uses React Router v6 for client-side routing. Routes are configured declaratively and support nested routes, protected routes, and permission-based access control.
Basic Route Configuration
Defining Routes
Routes are defined in src/routes/index.tsx:
import { lazy } from 'react';
import { DashboardOutlined } from '@ant-design/icons';
// Lazy load components
const ProductList = lazy(() => import('@/pages/products/ProductList'));
const ProductForm = lazy(() => import('@/pages/products/ProductForm'));
export const getRoutes = (): IRoute[] => {
return [
// Public routes (no authentication)
{
path: '/login',
element: withSuspense(Login),
index: true,
},
// Private routes (authentication required)
{
path: '/',
is_private: true,
children: [
{
path: '/',
element: withSuspense(Dashboard),
name: 'dashboard',
icon: <DashboardOutlined />,
index: true,
},
{
path: '/products',
name: 'products',
children: [
{
element: withSuspense(ProductList),
name: 'productList',
index: true,
},
{
path: 'create',
element: withSuspense(ProductForm),
name: 'productCreate',
hideInMenu: true, // Hide from sidebar menu
},
],
},
],
},
];
};
Route Structure
Route Interface
export interface IRoute {
path?: string; // Route path
element?: React.ReactNode; // React component
name?: string; // Route name (for menu)
icon?: React.ReactNode; // Icon for menu
children?: IRoute[]; // Nested routes
is_private?: boolean; // Require authentication
index?: boolean; // Index route
permissions?: string[]; // Required permissions
hideInMenu?: boolean; // Hide from sidebar
}
Route Types
1. Public Routes
Routes accessible without authentication:
{
path: '/login',
element: withSuspense(Login),
index: true,
}
2. Private Routes
Routes requiring authentication:
{
path: '/',
is_private: true,
children: [
{
path: '/dashboard',
element: withSuspense(Dashboard),
name: 'dashboard',
},
],
}
3. Nested Routes
Routes with children:
{
path: '/products',
name: 'products',
children: [
{
element: withSuspense(ProductList),
index: true,
},
{
path: ':id',
element: withSuspense(ProductDetail),
hideInMenu: true,
},
],
}
Adding Custom Routes
In Your Application
// src/App.tsx
import App from 'ez-console';
import { lazy } from 'react';
const CustomPage = lazy(() => import('@/pages/CustomPage'));
export default function MyApp() {
return (
<App
extraPrivateRoutes={[
{
path: '/custom',
element: withSuspense(CustomPage),
name: 'custom',
},
]}
/>
);
}
Using Route Transform
<App
transformRouter={(routes) => {
// Modify routes before rendering
return routes.map(route => {
if (route.path === '/dashboard') {
// Modify dashboard route
}
return route;
});
}}
/>
Navigation
Using React Router
import { useNavigate, useParams, useLocation } from 'react-router-dom';
export const ProductList: React.FC = () => {
const navigate = useNavigate();
const location = useLocation();
const params = useParams();
const handleCreate = () => {
navigate('/products/create');
};
const handleEdit = (id: string) => {
navigate(`/products/${id}/edit`);
};
const handleBack = () => {
navigate(-1); // Go back
};
return (
<Button onClick={handleCreate}>Create Product</Button>
);
};
Programmatic Navigation
// Navigate to route
navigate('/products');
// Navigate with state
navigate('/products', { state: { from: 'dashboard' } });
// Replace current route
navigate('/products', { replace: true });
// Go back
navigate(-1);
// Go forward
navigate(1);
Route Parameters
Dynamic Routes
// Route definition
{
path: '/products/:id',
element: withSuspense(ProductDetail),
}
// In component
import { useParams } from 'react-router-dom';
export const ProductDetail: React.FC = () => {
const { id } = useParams<{ id: string }>();
// Use id parameter
return <div>Product ID: {id}</div>;
};
Query Parameters
import { useSearchParams } from 'react-router-dom';
export const ProductList: React.FC = () => {
const [searchParams, setSearchParams] = useSearchParams();
const category = searchParams.get('category');
const page = searchParams.get('page') || '1';
const handleFilter = (newCategory: string) => {
setSearchParams({ category: newCategory, page: '1' });
};
return (
<div>
<Button onClick={() => handleFilter('electronics')}>
Electronics
</Button>
</div>
);
};
Protected Routes
Routes with is_private: true are automatically protected by the PrivateRoute component:
// Automatically handled by framework
{
path: '/',
is_private: true,
children: [
// All children require authentication
],
}
Permission-Based Routes
Routes can require specific permissions:
{
path: '/products',
name: 'products',
permissions: ['product:view'],
children: [
{
element: withSuspense(ProductList),
permissions: ['product:list'],
},
{
path: 'create',
element: withSuspense(ProductForm),
permissions: ['product:create'],
},
],
}
Routes are only visible in the menu if the user has the required permissions.
Menu Integration
Routes with name property automatically appear in the sidebar menu:
{
path: '/products',
name: 'products', // Menu label
icon: <ProductOutlined />, // Menu icon
children: [
{
element: withSuspense(ProductList),
name: 'productList', // Submenu label
},
],
}
Hide from Menu
{
path: '/products/:id/edit',
element: withSuspense(ProductForm),
hideInMenu: true, // Not shown in menu
}
Lazy Loading
Use lazy loading for better performance:
import { lazy } from 'react';
const ProductList = lazy(() => import('@/pages/products/ProductList'));
// Wrap with Suspense
export function withSuspense(Component: React.LazyExoticComponent<any>) {
return (
<Suspense fallback={<Loading />}>
<Component />
</Suspense>
);
}
Route Guards
Before Navigation
import { useBlocker } from 'react-router-dom';
export const useUnsavedChangesGuard = (hasUnsavedChanges: boolean) => {
useBlocker(
(tx) => {
if (hasUnsavedChanges) {
const confirmed = window.confirm(
'You have unsaved changes. Are you sure you want to leave?'
);
if (confirmed) {
tx.retry();
}
} else {
tx.retry();
}
},
hasUnsavedChanges
);
};
Breadcrumbs
Breadcrumbs are automatically generated from the route hierarchy:
// Route structure determines breadcrumbs
{
path: '/products',
name: 'products',
children: [
{
path: ':id',
name: 'productDetail',
},
],
}
// Breadcrumbs: Products > Product Detail
Best Practices
1. Organize Routes by Feature
// ✅ Good: Feature-based organization
{
path: '/products',
children: [
{ element: withSuspense(ProductList) },
{ path: 'create', element: withSuspense(ProductForm) },
],
}
// ❌ Bad: Flat structure
{
path: '/products',
element: withSuspense(ProductList),
},
{
path: '/products/create',
element: withSuspense(ProductForm),
}
2. Use Lazy Loading
// ✅ Good: Lazy load components
const ProductList = lazy(() => import('@/pages/products/ProductList'));
// ❌ Bad: Eager loading
import ProductList from '@/pages/products/ProductList';
3. Set Permissions
// ✅ Good: Specify permissions
{
path: '/products',
permissions: ['product:view'],
}
// ❌ Bad: No permission check
{
path: '/products',
}
4. Use Route Parameters
// ✅ Good: Dynamic routes
{
path: '/products/:id',
}
// ❌ Bad: Query parameters for IDs
{
path: '/products',
// Use ?id=123
}
Related Topics
- Application Shell - Main app structure
- State Management - State with navigation
- API Integration - API calls in routes
Need help? Ask in GitHub Discussions.