Layouts
Layouts in the Routing and data guide.
_layout.tsx is the layout of its folder. It renders <Outlet /> where the matched child goes, keeps its state while
the user moves between its children, and is rendered once on the server and once in the browser, like any route
component. A layout's id is its folder pattern plus /_layout; a group is part of the folder pattern, so
(account)/_layout.tsx has the id /(account)/_layout. Layouts nest by folder, and a layout can export every
route option a page can (loader, beforeLoad, head, notFound, error, ...).
import { A, Outlet } from "@shadow-js/router";
import type { JSX } from "@shadow-js/web";
export default function Layout(): JSX.Element {
return (
<div class="site">
<nav>
<A to="/">Home</A> <A to="/posts">Posts</A> <A to="/about">About</A>
</nav>
<Outlet />
</div>
);
}
import type { HeadTag, JSX } from "@shadow-js/web";
export const head = (): HeadTag[] => [{ tag: "title", children: "Home" }];
export default function Home(): JSX.Element {
return <h1>Home</h1>;
}
import type { HeadTag, JSX } from "@shadow-js/web";
export const head = (): HeadTag[] => [
{ tag: "title", children: "About" },
{ tag: "meta", props: { name: "description", content: "What this site is." } },
];
export default function About(): JSX.Element {
return <h1>About</h1>;
}
A nested layout wraps only its folder. This one adds a heading around the post routes:
import { Outlet } from "@shadow-js/router";
import type { JSX } from "@shadow-js/web";
export default function PostsLayout(): JSX.Element {
return (
<section class="posts">
<h2>Posts</h2>
<Outlet />
</section>
);
}
Parallel and intercepting folders (G-B5)#
The router has no parallel routes (a folder named @slot) and no intercepting routes ((.)x, (..)x and (...)x, as
folders or as files). The scan fails with ROUTE_PARALLEL_UNSUPPORTED or ROUTE_INTERCEPT_UNSUPPORTED, naming the
file and a fix: line. Before this check these names became literal URL segments (/@slot, /(.)photo) without a
word. Only folders are refused as parallel routes: a file named @john.tsx is still the page /@john.
Case sensitivity (G-B4)#
router.caseSensitive (the dusk() Vite option, default false) decides how static segments compare, for the scanner,
the server and the browser router alike. With false, /About and /about are one route, and the scanner refuses two
files that differ only in case. With true, they are two routes, and each answers its own URL: a request to /about
is a 404 when only /About exists. The option is written into the server manifest and the client entry at build time.
The base path follows the same rule. Param values are never compared.
The redirects, rewrites, headers and routeRules of dusk.config.ts follow the router's switch: with the default
(false) their literal parts compare without ASCII case, so a header rule or a redirect for /account/** also applies
to /ACCOUNT/data, which the router serves; with true they compare with case, as the routes do. Every spelling the
router serves for a path (case, %61, a dot segment the URL parser removes) is covered by the rules of that path.