Skip to main content

Routing & Navigation

DEVELOPER Beginner

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;
});
}}
/>

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.

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 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
}

Need help? Ask in GitHub Discussions.