Miolo Client-Side Routing
Client-side routing patterns using react-router v7 in miolo applications.
Entry Points (DO NOT MODIFY)
Miolo manages routing through react-router v7 with two entry points that should not be modified except for very specific needs:
Client Entry (BrowserRouter)
File: src/cli/entry-cli.jsx
import React from 'react'
import { hydrateRoot } from 'react-dom/client'
import { BrowserRouter } from 'react-router'
import { AppBrowser } from 'miolo-react'
import App from './App.jsx'
import '../static/style/globals.css'
const domNode = document.getElementById('root')
hydrateRoot(domNode,
<AppBrowser>
<BrowserRouter>
<App/>
</BrowserRouter>
</AppBrowser>
)
Do not modify - This is the client-side hydration entry point.
Server Entry (StaticRouter)
File: src/server/miolo/ssr/entry-server.jsx
Uses StaticRouter for server-side rendering. Do not modify except for specific SSR needs.
Developer Starting Points
Developers build the application from these two main components:
1. App.jsx - Context Providers
File: src/cli/App.jsx
The root application component that sets up context providers:
import React from 'react'
import { intre_locale_init } from 'intre'
import ThemeProvider from '#cli/context/theme/ThemeProvider.jsx'
import SessionProvider from '#cli/context/session/SessionProvider.jsx'
import UIProvider from '#cli/context/ui/UIProvider.jsx'
import DataProvider from '#cli/context/data/DataProvider.jsx'
import Index from '#cli/pages/Index.jsx'
intre_locale_init('es')
const App = () => {
return (
<ThemeProvider defaultTheme="dark" storageKey="vite-ui-theme">
<UIProvider>
<SessionProvider>
<DataProvider>
<Index/>
</DataProvider>
</SessionProvider>
</UIProvider>
</ThemeProvider>
)
}
export default App
Provider hierarchy (outer to inner):
- •ThemeProvider - Dark/light theme management
- •UIProvider - UI state (sidebar, modals, etc.)
- •SessionProvider - Authentication and user session
- •DataProvider - Application data and API calls
Key points:
- •Locale initialization with
intre_locale_init() - •Predefined providers - can be modified or extended
- •Renders
<Index/>as the main routing component
2. Index.jsx - Main Router
File: src/cli/pages/Index.jsx
The main routing component that splits between authenticated and unauthenticated states:
import React from 'react'
import useSessionContext from '#cli/context/session/useSessionContext.mjs'
import IndexOnline from '#cli/pages/IndexOnline.jsx'
import IndexOffline from '#cli/pages/IndexOffline.jsx'
export default function Index() {
const { authenticated } = useSessionContext()
if (!authenticated) {
return <IndexOffline/>
}
return <IndexOnline/>
}
Pattern:
- •Uses
SessionProviderto check authentication - •Routes to
IndexOfflinefor unauthenticated users - •Routes to
IndexOnlinefor authenticated users - •Miolo handles session management by default
Authenticated Routes (IndexOnline)
File: src/cli/pages/IndexOnline.jsx
Routes for authenticated users using react-router v7 with nested layout:
import React from 'react'
import { Routes, Route } from 'react-router'
import MainLayout from '#cli/layout/main-layout.jsx'
import Dashboard from '#cli/pages/dash/Dashboard.jsx'
import Security from '#cli/pages/security/Security.jsx'
export default function IndexOnline() {
return (
<Routes>
<Route path={'/'} element={<MainLayout/>}>
<Route index element={<Dashboard/>}/>
<Route path={'security'} element={<Security/>}/>
<Route path={'*'} element={<Dashboard/>}/>
</Route>
</Routes>
)
}
Key patterns:
- •Layout route -
MainLayoutas parent route element - •Nested routes - Child routes render inside layout's
<Outlet/> - •Index route - Default child route for
/path - •Catch-all route -
path={'*'}redirects to Dashboard
Layout component must use <Outlet/>:
import { Outlet } from 'react-router'
function MainLayout() {
return (
<div>
<Sidebar />
<main>
<Outlet /> {/* Child routes render here */}
</main>
</div>
)
}
Unauthenticated Routes (IndexOffline)
File: src/cli/pages/IndexOffline.jsx
Routes for unauthenticated users:
import React from 'react'
import { Routes, Route } from 'react-router'
import Login from '#cli/pages/offline/Login.jsx'
export default function IndexOffline() {
return (
<Routes>
<Route index element={<Login />} />
<Route path="*" element={<Login />} />
</Routes>
)
}
Key patterns:
- •No layout wrapper (full-page login/register)
- •Index route shows Login
- •Catch-all redirects to Login
- •Minimal routes for auth flow
Page Organization
Pages are organized in src/cli/pages/ by feature/section:
src/cli/pages/
├── Index.jsx # Main router (auth split)
├── IndexOnline.jsx # Authenticated routes
├── IndexOffline.jsx # Unauthenticated routes
├── dash/ # Dashboard pages
│ └── Dashboard.jsx
├── offline/ # Login/register pages
│ ├── Login.jsx
│ └── Register.jsx
├── security/ # User profile, security
│ └── Profile.jsx
└── todos/ # Feature-specific pages
├── TodosPage.jsx
└── TodoDetail.jsx
React Router v7 Patterns
Basic Route
<Route path="/todos" element={<TodosPage />} />
Route with Parameters
<Route path="/todo/:id" element={<TodoDetail />} />
Access params in component:
import { useParams } from 'react-router'
function TodoDetail() {
const { id } = useParams()
return <div>Todo ID: {id}</div>
}
Nested Routes
<Route path="/todos" element={<TodosLayout />}>
<Route index element={<TodosList />} />
<Route path=":id" element={<TodoDetail />} />
<Route path="new" element={<TodoCreate />} />
</Route>
Parent component needs <Outlet>:
import { Outlet } from 'react-router'
function TodosLayout() {
return (
<div>
<h1>Todos Section</h1>
<Outlet /> {/* Child routes render here */}
</div>
)
}
Programmatic Navigation
import { useNavigate } from 'react-router'
function MyComponent() {
const navigate = useNavigate()
const handleClick = () => {
navigate('/todos')
}
return <button onClick={handleClick}>Go to Todos</button>
}
Link Component
import { Link } from 'react-router'
function Navigation() {
return (
<nav>
<Link to="/dash">Dashboard</Link>
<Link to="/todos">Todos</Link>
<Link to="/profile">Profile</Link>
</nav>
)
}
Adding a New Route
- •
Create page component in
src/cli/pages/[feature]/:javascript// pages/items/ItemsPage.jsx export default function ItemsPage() { return <div>Items Page</div> } - •
Add route to
IndexOnline.jsx:javascriptimport ItemsPage from '#cli/pages/items/ItemsPage.jsx' <Route path="/items" element={<ItemsPage />} /> - •
Add navigation in layout/sidebar:
javascript<Link to="/items">Items</Link>
Best Practices
- •Don't modify entry points -
entry-cli.jsxshould remain unchanged - •Use Index.jsx pattern - Split authenticated/unauthenticated routes
- •Organize by feature - Group related pages in subdirectories
- •Use layouts - Wrap authenticated routes in
MainLayout - •Handle 404s - Always include catch-all route with
path="*" - •Use import aliases - Always use
#cli/prefix for imports - •Programmatic navigation - Use
useNavigate()for dynamic navigation - •Protected routes - Let
Index.jsxhandle auth, don't duplicate checks
Common Patterns
Redirect After Action
function CreateTodo() {
const navigate = useNavigate()
const handleSubmit = async (data) => {
await createTodo(data)
navigate('/todos') // Redirect after creation
}
return <form onSubmit={handleSubmit}>...</form>
}
Conditional Rendering by Route
import { useLocation } from 'react-router'
function Header() {
const location = useLocation()
const isDashboard = location.pathname === '/dash'
return (
<header>
{isDashboard && <DashboardActions />}
</header>
)
}
Query Parameters
import { useSearchParams } from 'react-router'
function SearchPage() {
const [searchParams, setSearchParams] = useSearchParams()
const query = searchParams.get('q')
const handleSearch = (newQuery) => {
setSearchParams({ q: newQuery })
}
return <div>Search: {query}</div>
}
Examples from miolo-sample
See actual implementations:
- •
src/cli/entry-cli.jsx- BrowserRouter entry point - •
src/cli/App.jsx- Context providers hierarchy - •
src/cli/pages/Index.jsx- Auth split routing - •
src/cli/pages/IndexOnline.jsx- Authenticated routes - •
src/cli/pages/IndexOffline.jsx- Unauthenticated routes