Routing
File-based routing
x uses the file system as your route table. Drop a file in src/pages/, get a route.
How it works
Every .tsx file in your pages directory becomes a route. The file path determines the URL pattern.
pages/index.tsx -> /pages/about.tsx -> /aboutpages/contact.tsx -> /contactpages/blog/index.tsx -> /blogpages/blog/[slug].tsx -> /blog/:slugpages/dashboard/ settings.tsx -> /dashboard/settings profile.tsx -> /dashboard/profilepages/_404.tsx -> catch-all 404Static routes
Simple files map to exact URL paths. pages/about.tsx becomes /about.
export default function About() { return <h1 className="text-3xl font-bold">About us</h1>;}Dynamic segments
Wrap a filename in square brackets to create a dynamic segment. The value is available via the params object in loaders.
import type { RouteProps, LoaderArgs } from "@thexjs/core";export async function loader({ params }: LoaderArgs) { const post = await getPost(params.slug); return { title: post.title, content: post.content };}export default function BlogPost({ loaderData }: RouteProps) { const { title, content } = loaderData as { title: string; content: string }; return ( <article> <h1 className="text-3xl font-bold">{title}</h1> <div>{content}</div> </article> );}Multiple dynamic segments work too: pages/product/[category]/[id].tsx → /product/:category/:id.
Catch-all routes
Prefix a dynamic segment with ... to match any number of remaining path segments. The full remaining path arrives as a single params value.
import type { RouteProps, LoaderArgs } from "@thexjs/core";export async function loader({ params }: LoaderArgs) { return { slug: params.slug };}export default function DocsPage({ loaderData }: RouteProps) { const { slug } = loaderData as { slug: string }; return <div>Viewing docs for: {slug}</div>;}/docs/routing → params.slug === "routing"; /docs/guides/overview → "guides/overview". Catch-alls match one or more segments, so they don't capture the parent route itself.
Nested routes with folders
Organize routes in folders for nested URL structures. Each folder can have its own index.tsx.
pages/dashboard/index.tsx -> /dashboardsettings.tsx -> /dashboard/settingsprofile.tsx -> /dashboard/profilebilling/index.tsx -> /dashboard/billinghistory.tsx -> /dashboard/billing/history
Catch-all 404 page
Create pages/_404.tsx to show a custom not-found page for unmatched routes.
export default function NotFound() { return ( <div className="text-center py-20"> <h1 className="text-6xl font-bold text-muted-foreground">404</h1> <p className="mt-3 max-w-[56ch] text-[15px] leading-relaxed text-fg-muted">Page not found</p> <a href="/docs" className="mt-6 inline-block text-primary hover:underline"> Go home </a> </div> );}