---
title: Files are your routes
description: How files in pages/ become the URLs of a Markless app.
sidebar: { label: Routing }
---

Last page, a saved value survived a reload. But a real app has more than one page, and each page needs a URL.

This section is for multi-page apps. A single-page app can call `render()` alone. See [Use Markless without a server](/start/browser-only).

`@markless/router` gives a multi-page app four things: file routes, links between pages, page data, and API routes. Every `npm create markless` starter already includes it. It builds on [Nitro](https://nitro.build), which deploys one app to many hosts.

Think of `pages/` as a street map. Each file path is an address. On each request, the router picks the first file that matches the URL.

Type an address in the bar, or pick one.

<AppRouteMapFigure />

Notice `/docs`. The catch-all file needs at least one more segment, so `404.tsrx` answers.

## The rules

| File in `pages/` | Route |
| --- | --- |
| `index.tsrx` | `/` |
| `about.tsrx` | `/about` (and `/about/`) |
| `docs/index.mdx` | `/docs` |
| `blog/[slug].tsrx` | `/blog/:slug` |
| `docs/[...slug].mdx` | `/docs/` plus one segment or more |
| `404.tsrx` | any URL with no match, status 404 |
| `500.tsrx` | a page that throws in a production build, status 500 |

Page files end in `.tsrx` or `.mdx`. The router skips every other file in `pages/`.

If two routes match one URL, a fixed segment wins over `[param]`. A `[param]` wins over `[...rest]`.

## Read the params

A page gets `params`, `url`, and `status` as props:

```tsrx pages/blog/[slug].tsrx
import type { PageProps } from '@markless/router';

export default function Post({ params }: PageProps<{ slug: string }>) @{
	<h1>Post {params.slug}</h1>
}
```

`/blog/hello` shows `Post hello`. A catch-all param is one string. For `/docs/guide/intro`, `slug` is `guide/intro`.

:::warning[Why does my build stop on a route?]
Two files for one route stop it, like `docs.tsrx` next to `docs.mdx`. A partial bracket (`post-[id].tsrx`) stops it. So does a catch-all that is not last, a nested `404.tsrx`, or any file in `pages/api/`. The message names the file.
:::

:::tip[Where is the layout file?]
There is none. `document.tsrx` in the app root wraps every page in `<Html>` from `@markless/router`. For a shared layout, write a component that takes `children`. Use it in each page.
:::

**Next:** How do you link to `/blog/hello` without a typo? [Pages and links →](/apps/pages-and-links)
