Routing & Transitions

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

Initializing diagram...

File System Routing

Every .tsx file in app/routes/ automatically becomes a route.

File PathURL PathDescription
app/routes/index.tsx/Home page
app/routes/about.tsx/aboutStatic route
app/routes/blog/index.tsx/blogNested static
app/routes/posts/[id].tsx/posts/123Dynamic segment
app/routes/docs/[...slug].tsx/docs/a/b/cCatch-all route

Example Structure

index.tsx
about.tsx

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: +100 points
  • Dynamic segment: +10 points
  • Catch-all segment: +1 point

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.


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:

index.tsx
~Header.tsx

Why Use ~?

  1. Keep routes clean — No accidental routes
  2. Colocate related code — Components next to pages
  3. 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/PostCard

Query 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=reactsearchParams.get('q') = "react"
  • /posts?sort=date&page=2searchParams.get('sort') = "date", searchParams.get('page') = "2"

See Router API for full reference.


Nested Layouts

Create shared layouts by using parent index.tsx:

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';
Cannot find module './~hooks/useAuth' or its corresponding type declarations.
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;
}
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.

On this page