> ## Documentation Index
> Fetch the complete documentation index at: https://better-router.bansal.io/llms.txt
> Use this file to discover all available pages before exploring further.

# File-based routing

> How files in src/routes become Hono paths, including dynamic params, groups, API routes, and static files.

better-router scans `src/routes` and registers each file on a Hono app. There is no route manifest you edit by hand.

## Pages vs APIs

* `.tsx` / `.jsx` files are React pages.
* `.md` / `.mdx` files are also pages. Same layouts, middleware, and loaders — see [Markdown pages](/markdown).
* `.ts` / `.js` files are API routes.
* `.server.tsx` files are never routes. They attach to the page with the same stem (`users/[id].tsx` + `users/[id].server.tsx`).

```
src/routes/index.tsx            →  GET /
src/routes/about.tsx            →  GET /about
src/routes/guide.mdx            →  GET /guide
src/routes/users/[id].tsx       →  GET /users/:id
src/routes/blog/[...slug].tsx   →  GET /blog/*
src/routes/api/hello.ts         →  GET|POST /api/hello
```

`index.tsx` in a folder is the folder URL: `src/routes/settings/index.tsx` → `/settings`.

## Dynamic segments

```tsx title="src/routes/users/[id].tsx" theme={null}
import { useParams } from "better-router/react";

export default function UserPage() {
  const { id } = useParams<{ id: string }>();
  return <h1>User {id}</h1>;
}
```

Catch-all segments use `[...slug]` and become Hono's `:slug{.*}`. The rest of the path is in `params.slug`.

Query strings are not part of the file name. Read them in a loader with `c.req.query()` or in the page with `useQuery()`.

```ts theme={null}
import type { LoaderArgs } from "better-router";

export function loader({ c }: LoaderArgs) {
  return { tab: c.req.query("tab") ?? "overview" };
}
```

## What is not a route

| File                                                     | Why                                                                                 |
| -------------------------------------------------------- | ----------------------------------------------------------------------------------- |
| `_layout.tsx`, `_document.tsx`, `_error.tsx`, `_404.tsx` | Special files. `_document.tsx` is the HTML shell — see [Layouts](/layouts#document) |
| `_middleware.ts`                                         | Nested middleware                                                                   |
| `_Card.tsx`                                              | `_` prefix, colocated component                                                     |
| `*.server.tsx`                                           | Server module for a page                                                            |
| `*.d.ts`                                                 | Types                                                                               |

## Route groups

`(admin)/settings.tsx` maps to `/settings`. The `(admin)` folder is only for organization — useful when two areas should not share a layout folder.

## Environment-only routes

```
src/routes/api/debug.dev.ts     # Vite dev only
src/routes/api/metrics.prod.ts  # production builds
```

The `.dev` / `.prod` suffix is stripped from the URL.

## API handlers

API files export HTTP methods. `defineHandler` turns return values into responses and can take Hono middleware.

```ts title="src/routes/api/users/index.ts" theme={null}
import { defineHandler, redirect } from "better-router";

export const GET = defineHandler((c) => {
  return { users: [] };
});

export const POST = defineHandler(async (c) => {
  const body = await c.req.json();
  return c.json(body, 201);
});
```

| Return                            | Response                                       |
| --------------------------------- | ---------------------------------------------- |
| object / array / number / boolean | JSON `200`                                     |
| string                            | HTML `200`                                     |
| `null` / `undefined`              | `204`                                          |
| `Response` / `c.json(...)`        | used as-is (keeps your status)                 |
| `redirect("/path")`               | `302` (`303` if you pass `redirect(url, 303)`) |
| `ReadableStream`                  | streamed body                                  |

Pages can export the same HTTP methods from `.server.tsx` if you want a JSON endpoint on the page URL (for example `POST /users/:id` next to the HTML page). If both `action` and `POST` exist, **`action` wins** for form posts.

## Validation

`defineHandler.withValidator()` accepts any [Standard Schema](https://standardschema.dev) library (Zod, Valibot, ArkType):

```ts theme={null}
import * as z from "zod";
import { defineHandler } from "better-router";

export const POST = defineHandler.withValidator({
  body: z.object({ name: z.string().min(1) }),
  query: z.object({ dryRun: z.string().optional() }),
  params: z.object({ id: z.string() }),
})(async (c, { body }) => {
  return { name: body.name };
});
```

Failed validation throws a `400` with `{ error: "Validation failed", issues }`.

## CSS, images, and `public/`

Import CSS from a layout or page. Vite handles it in dev and the client build:

```tsx title="src/routes/_layout.tsx" theme={null}
import "../styles.css";
```

Put files that should be served as-is in `public/` (`/favicon.ico`, `/robots.txt`). They land in `dist/client`.

There is no first-class CSS-in-JS runtime. Tailwind is a normal Vite setup: add Tailwind, import its CSS in `_document.tsx` or the root layout.

## TypeScript

See [TypeScript](/typescript) for `tsconfig` and how to share loader props with a page.
