Routing Guide
File-based routing, dynamic segments, catch-all routes, and navigation patterns.
Routing Guide
Manic features a file-system-based router that converts your app/routes/ directory into an optimized runtime registry with support for dynamic segments, nested layouts, and View Transitions.
Route Matching Flow
File System Routing
Every .tsx file in app/routes/ automatically becomes a route.
| File Path | URL Path | Description |
|---|---|---|
app/routes/index.tsx | / | Home page |
app/routes/about.tsx | /about | Static route |
app/routes/blog/index.tsx | /blog | Nested static |
app/routes/posts/[id].tsx | /posts/123 | Dynamic segment |
app/routes/docs/[...slug].tsx | /docs/a/b/c | Catch-all route |
Example Structure
Dynamic Segments
Single Dynamic Segment
Use [param] to capture a single URL segment:
// app/routes/posts/[id].tsx
import React from 'react';
import { } from 'manicjs';
export default function () {
const { } = ();
return <>Post ID: {.}</>;
}Matches:
/posts/123→{ id: "123" }/posts/hello-world→{ id: "hello-world" }- ❌
/posts/123/comments(too many segments)
Multiple Dynamic Segments
Nest folders to capture multiple params:
// app/routes/users/[userId]/posts/[postId].tsx
import React from 'react';
import { } from 'manicjs';
export default function () {
const { } = ();
return <>User {.}, Post {.}</>;
}Matches:
/users/42/posts/99→{ userId: "42", postId: "99" }
Catch-All Routes
Use [...param] to match any number of segments:
// app/routes/docs/[...slug].tsx
import React from 'react';
import { } from 'manicjs';
export default function () {
const { } = (); // slug = "guides/installation"
return <>Doc: {.}</>;
}Matches:
/docs/guides→{ slug: "guides" }/docs/guides/installation→{ slug: "guides/installation" }/docs/api/v2/endpoints/users→{ slug: "api/v2/endpoints/users" }
Route Scoring & Priority
When multiple routes could match a URL, Manic uses a scoring system to determine the winner:
Scoring Rules
- Static segment:
+100points - Dynamic segment:
+10points - Catch-all segment:
+1point
Example Precedence
URL: /posts/new
Route Scores:
├─ app/routes/posts/new.tsx [Score: 200] ← WINNER
├─ app/routes/posts/[id].tsx [Score: 110]
└─ app/routes/posts/[...slug].tsx [Score: 101]URL: /posts/123
Route Scores:
├─ app/routes/posts/new.tsx [Score: 200] ✗ No match
├─ app/routes/posts/[id].tsx [Score: 110] ← WINNER
└─ app/routes/posts/[...slug].tsx [Score: 101]Static routes always win. No need to order files — Manic determines precedence automatically.
Navigation
Client-Side Links
Use the <Link> component for navigation. It prevents full page reloads, prefetches the target chunk on hover/focus, and integrates with view transitions.
import React from 'react';
import { } from 'manicjs';
export function () {
return (
<>
< ="/">Home</>
< ="/about">About</>
< ="/posts/123">Post #123</>
</>
);
}Features:
- ✓ Automatic code-splitting
- ✓ Prefetch on hover/focus
- ✓ View Transitions (smooth animation)
- ✓ No page reload
See Router API for full prop reference.
Programmatic Navigation
Use useRouter() for logic-based navigation:
import React from 'react';
import { } from 'manicjs';
export function () {
const = ();
const = 'user@example.com';
const = 'secret';
const = async () => {
const = await ('/api/login', {
: 'POST',
: .({ , }),
});
if (.) {
.('/dashboard');
}
};
return < ={}>Login</>;
}See Router API for complete hook reference.
The ~ Convention
Files and folders prefixed with ~ are excluded from routing. Use this to colocate components, hooks, and utilities without polluting the route namespace:
Why Use ~?
- Keep routes clean — No accidental routes
- Colocate related code — Components next to pages
- Avoid naming conflicts — Use common names like
Layout,Card
Examples
// ✓ OK: Route
// app/routes/posts/[id].tsx
function () {
return <>Post Content</>;
}
export default function () {
return < />;
}
// ✓ OK: Ignored component
// app/routes/posts/~PostCard.tsx
export function () {
return <>Post Content</>;
}
// ❌ BAD: Creates unwanted route
// app/routes/posts/PostCard.tsx
// This would create a route at /posts/PostCardQuery Parameters
Use useQueryParams() to read URL search params:
import React from 'react';
import { } from 'manicjs';
declare const : any;
export function () {
const = ();
const = .('q');
const = (: string) => {
const = new ();
.('q', );
.history.replaceState({}, '', `?${.()}`);
};
return (
<>
<
={ || ''}
={(: any) => (.target.value)}
="Search..."
/>
{ && <>Results for: {}</>}
</>
);
}URL Examples:
/search?q=react→searchParams.get('q')="react"/posts?sort=date&page=2→searchParams.get('sort')="date",searchParams.get('page')="2"
See Router API for full reference.
Nested Layouts
Create shared layouts by using parent index.tsx:
// app/routes/admin/index.tsx (Layout)
import React from 'react';
import { } from 'manicjs';
export default function ({ }: { : React. }) {
return (
< ="admin">
<>
< ="/admin/users">Users</>
< ="/admin/settings">Settings</>
</>
<>{}</>
</>
);
}This is a convention — layout support depends on your router implementation using children prop.
Advanced Patterns
Protected Routes
import React from 'react';
import { } from 'manicjs';
import { } from './~hooks/useAuth';
export default function () {
const = ();
const { } = ();
React.(() => {
if (!?.isAdmin) {
.('/', { : true });
}
}, [, ]);
if (!?.isAdmin) return null; // Prevent flash
return <>Admin Panel</>;
}Redirect Logic
// app/routes/old-url.tsx
import React from 'react';
import { } from 'manicjs';
export default function () {
const = ();
React.(() => {
// Redirect to new location
.('/new-location', { : true });
}, []);
return null;
}Breadcrumbs
import React from 'react';
import { } from 'manicjs';
import { } from 'manicjs';
export function () {
const { } = ();
const = .('/').();
return (
<>
< ="/">Home</>
{.((, ) => {
const = '/' + .(0, + 1).('/');
return (
<React. ={}>
<>/</>
< ={}>{}</>
</React.>
);
})}
</>
);
}Best Practices
Use <Link> instead of <a> for client-side navigation. It's faster and provides prefetching.
Never manually manipulate window.location in Manic. Always use useRouter().navigate() or <Link>.
Query params are for filters, not state. Use React state for temporary UI state; use query params for shareable/bookmarkable state.
See the Router API reference for complete hook and component documentation.