# Have your agent write a page (/docs/agents/write-a-page)

> Prompt, record, safeParse, fix at the path, render; then the same check on every push, so no refused record ships.

**Using an agent? Paste this prompt**

```text
Read https://next.mdxui.dev/llms.txt and https://next.mdxui.dev/docs/agents/write-a-page.md.
Fetch https://next.mdxui.dev/schema.json and use it as the structured output schema for one page record.
Write the record for the page I describe. Keep every string inside its bound.
Parse it with PageSchema.safeParse. When it refuses, shorten the content at the path it names, never the bound.
Render it with renderPage and save the HTML file.
```

Copy the five-line prompt that runs these steps.

You describe the page; the agent writes the record, parses it, fixes each refusal at its path and renders it. Then one script keeps every record parsing on every push.

## Prerequisites

- `@mdxui/site` installed, as in the [Quickstart](https://next.mdxui.dev/docs/start/quickstart).
- A coding agent that can fetch an address and run a script.

**File: prompt**

**Write a page from a description**

```text
Read https://next.mdxui.dev/llms.txt and https://next.mdxui.dev/docs/agents/write-a-page.md.
Fetch https://next.mdxui.dev/schema.json and use it as the structured output schema for one page record.
Write the record for the page I describe. Keep every string inside its bound.
Parse it with PageSchema.safeParse. When it refuses, shorten the content at the path it names, never the bound.
Render it with renderPage and save the HTML file.
```

**File: refusal-url.ts**

```ts
import { PageSchema } from '@mdxui/site';

// Each refusal, then the address of its fix: the block's type, then the path after `block`, list indexes dropped.
export function refusalUrls(page: any): string[] {
  const result = PageSchema.safeParse(page);
  if (result.success) return [];
  return result.error.issues.flatMap(({ path, message }) => {
    const keys = (p: PropertyKey[]) => p.filter((s) => typeof s === 'string').join('.');
    const anchor = path[0] !== 'sections' ? `page.${keys(path)}` : path[2] === 'shell' ? `shell.${keys(path.slice(3))}` : [page.sections[path[1] as number]?.block?.type, keys(path.slice(3))].filter(Boolean).join('.');
    return [`${path.join('.')}: ${message}`, `https://next.mdxui.dev/docs/reference/refusals#${anchor}`];
  });
}
```

**File: check-records.ts**

```ts
// node check-records.ts: every record in pages/ parses, or each refusal prints with the address of its fix.
let refused = 0;
const files = (await readdir('pages', { recursive: true })).filter((f) => f.endsWith('.json')).sort();
for (const file of files) {
  const lines = refusalUrls(JSON.parse(await readFile(`pages/${file}`, 'utf8')));
  if (lines.length) refused++;
  for (let i = 0; i < lines.length; i += 2) console.error(`pages/${file}: ${lines[i]}\n  fix: ${lines[i + 1]}`);
}
if (refused) process.exitCode = 1;
else console.log(`${files.length} records parse`);
```

## Hand it the prompt and the schema

The prompt names what to read and the rule. `/schema.json` makes a good structured output schema: the model sees every bound as `maxLength`, `minItems` and `maxItems` while it writes. It cannot see every rule, so the next step still parses.

## Parse what it wrote

`PageSchema.safeParse` accepts the record or returns its issues, each a path and a message. Nothing renders until it accepts. A hero title of 71 characters returns:

```text
sections.1.block.title: Too big: expected string to have <=64 characters
```

## Fix it at the path

`refusalUrls` turns each issue into the address of its rule on [Refusals](https://next.mdxui.dev/docs/reference/refusals). The agent reads the rule, shortens the content at that path and parses again. It never raises a bound or drops the section to make the error go away.

## Render it

Once the record parses, `renderPage(record)` returns the page, and `renderPage(record, { theme })` the same page under another Theme. The agent hands back the record and the HTML; you review a JSON diff of words.

## Check on every push

Run `node check-records.ts` in CI. It parses every record in `pages/` and fails the run on any refusal, with the address of its fix. On records that parse:

```text title="node check-records.ts"
10 records parse
```

With one plan named `Every berth, all year`, 21 characters:

```text title="node check-records.ts"
pages/pricing.json: sections.1.block.tiers.0.name: Too big: expected string to have <=16 characters
fix: https://next.mdxui.dev/docs/reference/refusals#pricing.tiers.name
```

## Result

Every record the agent writes parses before it renders, and none that is refused reaches a deploy.

## Next steps

  - [Prompts](https://next.mdxui.dev/docs/agents/prompts): One for each task, Template and worked Site.
  - [schema.json](https://next.mdxui.dev/docs/agents/schema): What the schema carries, and what it leaves out.
  - [Refusals](https://next.mdxui.dev/docs/reference/refusals): Every rule, by path, with its message.

