Routing
File-based routing over src/app — pages, layouts, dynamic segments, and navigation.
Every src/app/**/page.tsx becomes a route automatically — no router config to
write. A Vite plugin scans src/app and emits a virtual route table that
<AppRouter> (mounted for you by the scaffold's entry) matches on every
navigation.
src/app/
├─ layout.tsx # wraps every route
├─ page.tsx # "/"
├─ about/
│ └─ page.tsx # "/about"
└─ blog/
└─ [slug]/
└─ page.tsx # "/blog/:slug"Layouts
layout.tsx wraps every route beneath it (nested layouts compose root → leaf).
Your root layout should wrap its children in <App> — a full-window frame
component from murasaki:
import type { ReactNode } from 'react'
import { App, useContextMenu } from 'murasaki'
export default function Layout({ children }: { children: ReactNode }) {
useContextMenu([{ label: 'Reload', shortcut: 'command,R', action: () => location.reload() }])
return <App className="flex items-center justify-center">{children}</App>
}Dynamic segments
A [param] folder captures a URL segment; read it with useParams():
import { useParams } from 'murasaki'
export default function BlogPost() {
const { slug } = useParams()
return <p>Post: {slug}</p>
}Static segments always win over dynamic ones, and a more-specific match (more
literal segments) wins over a less-specific one — the same precedence Next.js
uses. A (group) folder — parens, no brackets — organizes routes without
adding a URL segment.
Loading, error, and not-found boundaries
Drop these files next to (or above) a page.tsx:
loading.tsx— shown while the page suspends (wrapped in a<Suspense>).error.tsx— an error boundary; exports a component receiving{ error, reset }. Catches render errors thrown under that route.not-found.tsx— rendered when no route matches; the nearest one in the URL's ancestor chain wins.
Each is scoped to its own subtree, the same as Next.js App Router.
Navigation
Use <Link> for client-side navigation (no full reload):
import { Link } from 'murasaki'
<Link href="/about">About</Link>useRouter() gives you push / replace / back / pathname imperatively,
and usePathname() reads the current path.
A plain <a href="https://..."> to an off-origin URL opens in the user's
default system browser, not inside the app window — you don't need to
special-case external links.