RRelayer
Home/Features

Per-Page Scripts#

Instead of putting everything into one global bundle, a page (or a higher-level layout) can declare its own external scripts.

Function style#

return function (PageContext $ctx): Closure {
    $ctx->js('/assets/chart.js', defer: true);

    return fn (): Element => <canvas id="chart"></canvas>;
};

Class style#

final class Dashboard extends LayoutComponent
{
    public function render(): Element
    {
        $this->addJs('/assets/dashboard.js', module: true);

        return <div>{...$this->getChildren()}</div>;
    }
}

For a page class, PageComponent::addJs(), and for a layout, LayoutComponent::addJs(), are used the same way.

Behavior#

  • Output at the end of <body>, after the main usePHP bundle,

in declaration order. A layout's scripts come before the page's, and an outer (root) layout comes before an inner one.

  • src-only. The flags are defer / async / module

(type="module"). For inline JS, use $document->addHeadHtml() (the same hook as the islands loader).

  • No deduplication. If a layout and a page declare the same src,

both are output (the same "just declare, do not reconcile" policy as metadata()).

A real example on this site#

highlight.js for code highlighting is loaded only on documentation pages that have <pre><code>.

// src/Pages/docs/[slug]/page.psx
$ctx->js('https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/highlight.min.js', defer: true);

The home, search, and 404 pages have no code blocks, so they do not load highlight.js. Inline / <link> such as initialization and theme CSS go on the $document->addHeadHtml() side (public/index.php), and only the library itself is declared per page — that is the division of labor. The Tailwind/dark-mode injection uses the same mechanism.

Last updated: 2026-05-19
Change history (1)
  • Created