Pages and Layouts
# Pages
Each page.ts file exports a default component that receives PageProps:
// src/app/about/page.ts
import { html } from "@deijose/nix-js";
import type { PageProps } from "@deijose/nix-js-kit";
import { load } from "./page.data.ts";
export default function AboutPage({ data, params, searchParams }: PageProps<typeof load>) {
return html`
<article>
<h1>${data.title}</h1>
<p>Query: ${searchParams.get("tab") ?? "default"}</p>
</article>
`;
}# PageProps
interface PageProps<TData, TLayout> {
data: InferLoaderData<TData>; // loader return value
layoutData?: InferLoaderData<TLayout>; // nearest layout.data.ts return
params: RouteParams; // dynamic segment values
searchParams: URLSearchParams; // query string
form?: unknown; // last action result (POST)
}:::tip
PageProps<typeof load> automatically infers the data type from your loader — no manual typing needed.
:::
# Layouts
layout.ts files wrap all pages in the same directory and below:
// src/app/layout.ts
import { html } from "@deijose/nix-js";
import type { LayoutProps } from "@deijose/nix-js-kit";
export default function RootLayout({ children }: LayoutProps) {
return html`
<html lang="en">
<body>
<header><nav>...</nav></header>
<main id="app">${children}</main>
<footer>© 2025</footer>
</body>
</html>
`;
}# Nested layouts
Layouts chain automatically. For src/app/blog/[slug]/page.ts:
src/app/layout.ts(root)src/app/blog/layout.ts(blog section)
Each layout receives children — the rendered output of the next layout or page in the chain.
# LayoutProps
interface LayoutProps<TData> {
children: unknown; // child page/layout output
data?: InferLoaderData<TData>; // layout.data.ts return value
}# Layout data loaders
layout.data.ts provides data to the layout:
// src/app/layout.data.ts
import type { PageDataLoad } from "@deijose/nix-js-kit";
export const load: PageDataLoad = async () => {
return { siteName: "My Blog", nav: ["home", "about", "blog"] };
};// src/app/layout.ts
import { html } from "@deijose/nix-js";
import type { LayoutProps } from "@deijose/nix-js-kit";
import { load } from "./layout.data.ts";
export default function RootLayout({ children, data }: LayoutProps<typeof load>) {
return html`
<html lang="en">
<body>
<header><h1>${data.siteName}</h1></header>
<main id="app">${children}</main>
</body>
</html>
`;
}# HTML attributes
Loaders can return htmlAttributes to set attributes on the <html> element:
// src/app/layout.data.ts
export const load = async ({ request }) => {
const theme = request?.headers.get("Cookie")?.includes("theme=dark")
? "dark" : "light";
return {
htmlAttributes: { "data-theme": theme },
};
};# Head scripts
Loaders can return headScripts — inline scripts injected into <head> that run before the first paint:
export const load = async ({ request }) => {
const theme = getThemeFromCookie(request);
return {
headScripts: [
`(function(){document.documentElement.setAttribute('data-theme','${theme}');})();`,
],
};
};:::note Head scripts are the standard no-flash bootstrap pattern — they run synchronously before the deferred client bundle, preventing theme flashes on static pages. :::