# Pages and routing - SSR websites

> How the files of pages/ become the routes of an SSR website - pages, dynamic parts, catch-all routes, groups, layouts, middlewares, 404 and error pages, endpoints and public files.

Source: https://docs.apimaker.dev/v4/docs/ssr/pages-and-routing.html

The files of `pages/` are the routes of a website. The path of a file is the path it answers.

| File | Path | What it is |
|---|---|---|
| `pages/index.tsx` | `/` | A page |
| `pages/about.tsx` | `/about` | A page |
| `pages/blog/index.tsx` | `/blog` | A page |
| `pages/blog/[slug].tsx` | `/blog/hello` | A page with one part which takes a value : `ctx.params.slug` |
| `pages/docs/[...path].tsx` | `/docs/a/b` | One part or more : `ctx.params.path` is `['a', 'b']` |
| `pages/docs/[[...path]].tsx` | `/docs`, `/docs/a/b` | Any number of parts, none too |
| `pages/(shop)/cart.tsx` | `/cart` | A folder between brackets is no part of the path : it gathers pages under a layout |
| `pages/api/items.ts` | `/api/items` | An endpoint : functions named after methods |
| `pages/rss.xml.ts` | `/rss.xml` | An endpoint with a dot in its path |
| `pages/_layout.tsx` | | The HTML around the pages of its folder and of the folders below it |
| `pages/_middleware.ts` | | Code which runs before the pages and endpoints of its folder |
| `pages/_404.tsx`, `pages/_error.tsx` | | What a path no page answers shows ; what a page which failed shows |
| `pages/_parts/…` | | A folder whose name starts with `_` holds no routes |

When two routes take a path, the one whose parts are fixed earliest wins : `/blog/new` is answered by `blog/new.tsx` before `blog/[slug].tsx`, which comes before `blog/[...rest].tsx`, which comes before `blog/[[...rest]].tsx`. Two files of the same route (`about.tsx` and `about/index.tsx`) are a problem of the website, which the panel shows.

## A page

A `.tsx` file of `pages/` exports its component as `default`. It may export :

| Export | What it is |
|---|---|
| `default` | The component : a function of `{ data, ctx, params, actionData }` which returns JSX. It may be `async`. |
| `loader(ctx)` | Reads on the server what the page shows ; what it returns is `data`. See [data](https://docs.apimaker.dev/v4/docs/ssr/data.html). |
| `action(ctx)` | Takes a form the page posts. See [forms](https://docs.apimaker.dev/v4/docs/ssr/forms.html). |
| `config` | Its title, its description, who may open it, how long it is kept, the UI libraries it loads. See [reference](https://docs.apimaker.dev/v4/docs/ssr/api-reference.html#pageconfig). |
| `head(props)` | What it adds to the head : `meta`, `links`, or JSX. See [SEO](https://docs.apimaker.dev/v4/docs/ssr/seo.html). |

**pages/blog/[slug].tsx**

```tsx
import { notFound } from 'am-ssr';
import type { PageConfig, PageContext, PageProps } from 'am-ssr';

export async function loader(ctx: PageContext) {
    const [post] = await ctx.db.query({ instance: 'mongodb', database: 'blog', table: 'posts', find: { slug: String(ctx.params.slug) }, limit: 1 });
    if (!post) notFound('This post does not exist.');
    return { post };
}

// The title can be made from the data of the page.
export const config: PageConfig = { title: (data) => data.post.title };

export default function Post({ data }: PageProps<typeof loader>) {
    return (
        <article>
            <h1>{data.post.title}</h1>
            <p>{data.post.summary}</p>
        </article>
    );
}
```

## JSX

JSX is HTML with values in braces, compiled to templates when the file is saved : no virtual DOM, nothing walked when the page renders.

- `class` takes a text, a list (`['btn', active && 'active']`) or an object (`{ active: true }`). `className` is the same.
- `style` takes a text or an object : `{ color: 'red', fontSize: 14 }` (`px` added to numbers).
- A boolean attribute is written for `true` and left out for `false` : `<option selected={x === y}>`.
- `false`, `null` and `undefined` write nothing ; `0` writes `0`.
- A function given to an attribute (`onclick={...}`) is left out : nothing runs in the browser unless a script does it.
- `<script>` and `<style>` take their text as it is (a script gets the nonce of the page by itself) ; every other text is escaped.
- `<>…</>` writes its children without an element around them.
- `<Tag>` where `Tag` is a variable holding a name (`'h2'`) writes that element.

## Layouts

`_layout.tsx` wraps every page of its folder and of the folders below it : `children` is the page, or the layouts inside this one. The layout at the root writes `<html>` : the server writes the title, the description, the CSRF token and the UI libraries into its head.

**pages/_layout.tsx**

```tsx
import { asset } from 'am-ssr';
import type { LayoutProps } from 'am-ssr';

export default function Layout({ children, ctx }: LayoutProps) {
    return (
        <html lang="en">
            <head>
                <meta charset="utf-8" />
                <meta name="viewport" content="width=device-width, initial-scale=1" />
                <link rel="stylesheet" href={asset('/styles.css')} />
            </head>
            <body>
                <nav><a href="/">Home</a> <a href="/blog">Blog</a></nav>
                {ctx.flashes.map((flash) => <p class={'flash ' + flash.type}>{flash.text}</p>)}
                <main>{children}</main>
            </body>
        </html>
    );
}
```

A layout may have a `loader` (its `data`), a `config` (it applies to every page inside it : `access`, `cache`, `libraries`) and a `head`. The loaders of the layouts and of the page run at the same time.

A group folder gives some pages a layout of their own without changing their paths : `pages/(account)/_layout.tsx` wraps `pages/(account)/orders.tsx`, which is `/orders`.

## Middlewares

`_middleware.ts` runs before every page and endpoint of its folder and of the folders below it, the outer ones first. Return an answer to stop there, nothing to go on.

**pages/admin/_middleware.ts**

```ts
import type { PageContext } from 'am-ssr';

export default async function middleware(ctx: PageContext) {
    ctx.setHeader('x-robots-tag', 'noindex');
    if (!ctx.auth.hasGroup('admin')) ctx.forbidden('This part of the website is for the group admin.');
}
```

> **Tip : Prefer `access` for who may open a page**
>
> `access` in the `config` of a page or a layout is read when the file is saved, and the server refuses a visitor before any code runs. A middleware is code : use it for what values can not say.

## 404 and error pages

`_404.tsx` answers a path nothing answers, and `notFound()`. `_error.tsx` answers what a page threw (`error(400, '…')`, `forbidden()`) or what failed. Both render in the layouts of their folder ; the nearest to the path answers. They get `error` : `{ status, message, stack, errors }`.

- A visitor sees the message of an error thrown on purpose (`notFound('No such product.')`) ; for anything else, a sentence. The preview of the panel shows the message and the stack of every error, with the lines of your files.
- A request which asks for JSON (`accept: application/json`) or an endpoint gets `{ success: false, status, message }`.
- Without `_404.tsx` or `_error.tsx`, the server answers with a plain page of its own.

**pages/_error.tsx**

```tsx
import type { ErrorProps, PageConfig } from 'am-ssr';

export const config: PageConfig = { title: 'Error', index: false };

export default function ErrorPage({ error }: ErrorProps) {
    return (
        <section>
            <h1>{error.status}</h1>
            <p>{error.message}</p>
            {error.stack && <pre>{error.stack}</pre>}
        </section>
    );
}
```

## Endpoints

A `.ts` (or `.js`) file of `pages/` is an endpoint : its functions are named after the methods they answer — `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, or `ALL` for every method. `HEAD` is answered by `GET`. A method it has not is a `405` with `Allow`.

**pages/api/notes.ts**

```ts
import { json } from 'am-ssr';
import type { PageConfig, PageContext } from 'am-ssr';

export const config: PageConfig = { access: 'authenticated' };

export async function GET(ctx: PageContext) {
    // An object is JSON.
    return ctx.db.query({ instance: 'mongodb', database: 'app', table: 'notes', find: {}, limit: 50 });
}

export async function POST(ctx: PageContext) {
    const text = String(ctx.body?.text ?? '').trim();
    if (!text) return json({ message: '`text` is a text.' }, { status: 400 });
    return json(await ctx.db.saveSingleOrMultiple({ instance: 'mongodb', database: 'app', table: 'notes', saveData: { text } }), { status: 201 });
}
```

| An endpoint returns | The answer |
|---|---|
| An object, a list, a number | JSON |
| A text | `text/plain` |
| `json(data, { status, headers })` | JSON with that status |
| `ctx.text(body, status)`, `ctx.html(body, status)` | A text, or HTML (`ctx.html(await Card({ ... }))` sends a component without the layout) |
| `redirect('/path', 303)` | A redirect |
| Nothing | `204 No Content` |

An endpoint which takes `POST`, `PUT`, `PATCH` or `DELETE` asks for the CSRF token of the visitor (`x-csrf-token`, or the field `_csrf` of a form). An endpoint other servers call (a webhook) says `csrf: false` in its `config` and checks a secret of its own. See [forms](https://docs.apimaker.dev/v4/docs/ssr/forms.html#csrf).

## Public files

`public/` holds files sent as they are : `public/logo.png` is `/logo.png`. A file of `public/` wins over a page of the same path.

- `asset('/logo.png')` writes its address with the hash of its content : `/ssr/acme/shop/logo.png?v=3fa2c9e1`. Browsers keep such an address for a year (`immutable`) and take the new one when the file changes.
- Without the hash, the address is answered with an `ETag` : browsers ask again, and get `304` when nothing changed.
- Texts are compressed (Brotli or gzip) the first time a browser asks for them.
- The panel uploads images, fonts and any other file into `public/` ; the code of the server never runs them.

## Addresses

Write the addresses of the website from its root : `href="/about"`. The server writes the base path in front of them by itself — `/ssr/acme/shop/about`, or `/about` behind a domain of its own — in `href`, `src`, `action`, `formaction`, `poster` and the `hx-get`… attributes of htmx.

- In a value which is no attribute (a text of a script, a redirect of your own), use `ctx.link('/about')`.
- `withoutBase('/api/custom-api/…')` is an address of the server outside the website.
- An address of another site (`https://…`) is written as it is ; `javascript:` is refused when the file is saved.
