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:

{
  "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).

  1. 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:

{% 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:

<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 route uses the marketing layout to wrap the content-only welcome page template.