AkurAI Build
Menu

Public repository / 439f0dcabf5cdf2b120c7724be895cf1df30507f

AkurAI-Framework / site/content/docs/layouts.md

Latest change 7d64b4511fc8d94d569ca5b7df933a213ab69a05 - feat(cli): route layouts (shared shell templates) by Ólafur Búi Ólafsson

# Route layouts

A **layout** is a shared shell template that wraps a page. It is the
AkurAI-Framework take on the nested-layout idea from SvelteKit and Next.js:
write the surrounding chrome once, then point any number of routes at it.

Layouts are opt-in and fully backward compatible — a route without a layout
renders exactly as it did before.

## Declaring a layout

Add an optional `"layout"` field to a route in `backend/routes.json`. Its value
is the file stem of a template in `frontend/` (a `*.html` file), just like
`template`:

```json
{
  "routes": [
    { "path": "/", "template": "index" },
    {
      "path": "/welcome",
      "template": "welcome",
      "layout": "marketing"
    }
  ]
}
```

Existing fields keep working untouched. You can combine `layout` with the SEO
`title`/`description` fields on the same route.

## How composition works

When a matched route declares a layout, the server renders in two passes:

1. The page `template` is rendered with the usual context (route `params`, any
   per-route `meta_*`, and the project-wide `page.json` context).
2. That rendered HTML is injected back into the **same context** under the key
   `content`, and the `layout` template is rendered. The layout pulls the page
   output in with the raw-output tag `{{{ content }}}`.

This is the same one-pass-into-a-shell composition the docs pages already use —
now available declaratively to any route.

A route with **no** `layout` renders in a single pass, exactly as before.

## Writing a layout template

A layout owns the document shell (head, header, footer) and drops the page where
`{{{ content }}}` appears:

```html
{% include "header" %}
    <main class="container">
      <div class="marketing-shell">
        {{{ content }}}
      </div>
    </main>
{% include "footer" %}
```

The matching page template then holds **only its own content** — no
`<head>`, header, or footer, since the layout supplies those:

```html
<section class="page-head">
  <h1>Welcome to the marketing shell</h1>
  <p class="lede">This fragment is wrapped by the shared layout.</p>
</section>
```

The page fragment and the layout both see the full render context, so either can
read `{{ params.* }}`, `{{ title }}`, `nav`, and anything else from `page.json`.

## Live demo

The site itself ships a working example: the [`/welcome`](/welcome) route uses
the `marketing` layout to wrap the content-only `welcome` page template.