[]previous and next page

Layouts

Layouts in the Routing and data guide.

For agentsUpdated

_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, ...).

src/routes/_layout.tsxtsx
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>
  );
}
src/routes/index.tsxtsx
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>;
}
src/routes/about.tsxtsx
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:

src/routes/posts/_layout.tsxtsx
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.