# Guide for AI assistants - SSR websites

> What an AI assistant needs to write the pages of an SSR website of API Maker right the first time - the files, the rules, the APIs, the security rules, and complete examples to copy.

Source: https://docs.apimaker.dev/v4/docs/ssr/ai-guide.html

This page is written for an assistant which writes the files of a website of API Maker. Read it whole before you write a file.

## The website

- A website is files. Its pages are TSX, rendered **on the server** by QuickJS : no React, no hooks, no state, no `useEffect`. A component is a function of its props which returns JSX ; it may be `async`.
- `pages/` holds the routes : `index.tsx` is `/`, `blog/[slug].tsx` is `/blog/:slug`, `docs/[...path].tsx` takes several parts, `(group)/` is no part of the path, a `.ts` file is an endpoint (`export function GET(ctx)`).
- `_layout.tsx` wraps the pages of its folder (it gets `children`), `_middleware.ts` runs before them, `_404.tsx` and `_error.tsx` answer errors (they get `error`).
- `components/`, `lib/` : code the pages import with `@/components/Card` (`@/` is the root of the website), `./x`, `../x`. `data/*.json` is imported as a module. `public/` is sent as it is.
- `site.config.ts` : `export default defineSite({ ... })` with **values only** (no functions, no variables). `libraries.json` : the UI libraries of the browser.
- The only modules a file imports are files of the website, `'am-ssr'` and `'types'`. There is no npm on the server side.

## A page, complete

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

export const config: PageConfig = {
    title: (data) => data.product.name,          // a value, or (data, ctx) => value
    description: (data) => data.product.summary,
    access: 'public',                            // 'public' | 'authenticated' | { groups: ['admin'] }
    cache: { maxAge: 60, scope: 'public' },      // only for pages which show nothing of the visitor
};

export async function loader(ctx: PageContext) {
    const [product] = await ctx.db.query({
        instance: 'mongodb', database: 'shop', table: 'products',
        find: { slug: String(ctx.params.slug) },   // a value from the visitor, made a text
        limit: 1,
    });
    if (!product) notFound('No such product.');
    return { product };
}

export default function ProductPage({ data }: PageProps<typeof loader>) {
    return (
        <article>
            <h1>{data.product.name}</h1>
            <p>{formatCurrency(data.product.price, 'USD')}</p>
            <a href="/products">All products</a>
        </article>
    );
}
```

## Rules

1. **Write addresses from the root of the website** : `href="/about"`, `src="/logo.png"`, `action="/login"`. The server adds the base path (`/ssr/<apiPath>/<name>`, or nothing behind a domain). Do not write `/ssr/<apiPath>/<name>` yourself. In a value which is no attribute, use `ctx.link('/about')`.
2. **Static files** : `asset('/styles.css')` (hash in the address, kept a year).
3. **Every value is escaped**. Write `{text}` ; never `raw()` or `dangerouslySetInnerHTML` with what a visitor wrote.
4. **No event attributes** : `onclick="..."` does not run (CSP). Add a `<script>` (it gets the nonce by itself) which listens : `document.getElementById('x').addEventListener('click', …)`.
5. **Data for a script** : `<script>{`const DATA = ${serialize(data)};`}</script>`.
6. **Forms** post to their own page : `<form method="post">` gets its CSRF token by itself ; the page exports `action(ctx)` ; read `ctx.form` ; return `{ values, errors }` with `ctx.status(422)` on a mistake ; on success `ctx.flash('success', '…')` then `redirect('/path', 303)`.
7. **Scripts which POST** send `x-csrf-token: window.__AM_SSR__.csrf` and use `window.__AM_SSR__.base + '/api/...'`.
8. **Data** : `ctx.db.<method>({ instance, database, table, ... })` reaches only the tables `site.config.ts` lists in `data.read` / `data.write` (`'instance/database/table'` or `{ table, where, set, requireUser, fields, allow }`). When you add a call to a new table, **add the table to `site.config.ts`**. For data of a visitor, add a rule `where: { owner_id: '$user._id' }` rather than filtering in the page.
9. **Never pass an object of the visitor as a query** : `find: { status: String(ctx.query.status) }`, never `find: ctx.query`. Escape regular expressions.
10. **Who may open a page** : `config.access`, or `access` of a `_layout.tsx` for a whole folder, or `defaultAccess` of the website. Do not check it only in the loader.
11. **Sign-in** : `await ctx.auth.login({ provider: '<name in auth.providers>', username, password })` in an action, inside `try/catch` (`HttpError` with `status < 500` is a wrong password : answer `401` with one sentence for both cases). Sign-out is a `POST` endpoint calling `ctx.auth.logout()`.
12. **Redirects** to another host need `{ external: true }`. A `next` of the query is a path : check `next.startsWith('/') && !next.startsWith('//')`.
13. **Cache** : `cache: { maxAge, scope: 'public' }` only for pages which show nothing of the visitor (no `ctx.user`, no cookies). Drop with `tags` + `ctx.revalidate('tag')` after a change.
14. **Never keep `ctx` or a visitor's data in a variable of a module** : modules are shared by every visitor.
15. **No Node.js** : no `fs`, `process`, `Buffer`, `require('x')` of npm, `setTimeout`. Use `ctx.fetch` (hosts listed in `data.fetch`), `ctx.cache`, `crypto.randomUUID()`, `URL`, `TextEncoder`.
16. **Endpoints** return an object (JSON), a text, `json(data, { status })`, `ctx.html(await Component(props))` for HTML without the layout, or nothing (`204`).
17. **Browser libraries** (Bootstrap, Tailwind, Chart.js, htmx...) go in `libraries.json` (`{ "id": "bootstrap" }`) ; a page may limit them with `config.libraries: ['chartjs']`. Never add a `<script src="https://cdn...">` by hand : the CSP refuses it.
18. **Types** : `PageProps<typeof loader>`, `LayoutProps`, `ErrorProps`, `PageContext`, `PageConfig`, `Head` from `'am-ssr'` (`import type`).

## The checklist of a new page

- [ ] The file is in `pages/` at the path it answers ; a dynamic part reads `ctx.params.<name>` (a text, or a list for `[...]`).
- [ ] `config` with `title` (and `description` for public pages), `access` when it is not public.
- [ ] Every table it reads or writes is in `site.config.ts`, with a row rule for the data of visitors.
- [ ] Values of the visitor are made texts or numbers before they reach a query.
- [ ] A missing record is `notFound()`.
- [ ] Forms : `method="post"`, an `action`, checks on the server, `303` after success.
- [ ] Links are written from the root of the website.

## site.config.ts, complete

```ts
import { defineSite } from 'am-ssr';

export default defineSite({
    title: 'My shop',
    titleTemplate: '%s · My shop',
    description: 'What the website is.',
    lang: 'en',
    domains: [],
    auth: { providers: ['customers'], loginPath: '/login', sessionDays: 30 },
    defaultAccess: 'public',
    data: {
        read: ['mongodb/shop/products', { table: 'mongodb/shop/orders', where: { customer_id: '$user._id' }, requireUser: true }],
        write: [{ table: 'mongodb/shop/orders', where: { customer_id: '$user._id' }, requireUser: true, allow: ['create'] }],
        fetch: [],
        allowSys: false,
    },
    security: { csp: 'strict' },
    seo: { sitemap: true, robots: 'allow' },
    redirects: [],
});
```

## Where to look

- [Starter template](https://docs.apimaker.dev/v4/docs/ssr/templates.html) : one page per feature, with comments.
- [Examples](https://docs.apimaker.dev/v4/docs/ssr/examples.html) : recipes to copy.
- [Reference](https://docs.apimaker.dev/v4/docs/ssr/api-reference.html) : every function and every member of `ctx`.
- The declarations : `am-ssr.d.ts` next to each website in the git repository.
