Templates
Templates in the Routing and data guide.
_template.tsx is the template of its folder. It sits in the tree where a layout does: it renders <Outlet /> where
the matched child goes, and it wraps the pages of its folder and the folders below. The difference is what happens on
a navigation. A layout is built once and keeps its state while the user moves between its children. A template is
built again, so its state starts over:
_layout.tsx |
_template.tsx |
|
|---|---|---|
| Built | Once per route; kept while the route stays on screen | Again whenever the pathname changes |
State created inside it (signals, effects, onMount) |
Survives navigations between its children | Starts from the first value on every navigation; the previous instance is disposed |
| Search or hash change | Kept | Kept: only the pathname counts |
| Needs a layout in the folder | n/a | No. With a layout in the same folder the template sits inside it (layout, then template, then page); without one it wraps the folder's pages directly |
When a folder has both, the order is layout, then template, then the page. Nested templates compose from the outside in,
and every template on the page is keyed by the same thing, the matched pathname, so a navigation that changes the
pathname rebuilds all of them, together with everything under them (the page and any nested layout, whose state
starts over too). Use a template for what has to restart per page: an enter animation, a per-page counter or form
draft, an effect that should run again (a page-view ping in onMount).
Saved as src/routes/posts/_template.tsx:
import { useSignal } from "@shadow-js/core";
import { Outlet } from "@shadow-js/router";
import type { JSX } from "@shadow-js/web";
export default function PostsTemplate(): JSX.Element {
// Created again on every navigation inside /posts, so each page starts with a count of 0.
const [clicks, setClicks] = useSignal(0);
return (
<section class="posts-enter">
<button type="button" onClick={() => setClicks(clicks() + 1)}>
Clicked {clicks()} times on this page
</button>
<Outlet />
</section>
);
}
What to know:
- The key is the whole pathname, so the rule is broad: going from
/posts/1to/posts/2(the same page with a new param) rebuilds the template, and so does going from/posts/1/commentsto/posts/1/likes, a change two levels below it. Next.js describes its templates as remounting when the segment below them changes; do not rely on this router remounting in exactly the same cases as Next.js does. - A rebuild does not refetch anything: loader data lives in the router's cache, outside the template.
- The server renders the template once and the browser hydrates the same markup; the first render on the client does not rebuild it.
- A template needs a default export (
ROUTE_NO_EXPORTSotherwise) and is not an API route. A_template.tsxwith no pages under it is not a route and is ignored. - The template's id is its folder pattern plus
/_template(/posts/_template), androuteMetalists it with the kindlayout, since it is a pathless route that wraps its children. - An app without a
_template.tsxis unchanged: the generated route tree is the same text as before and nothing about templates is imported. In an app with one, the tree wraps each template component withdefineTemplatefrom@shadow-js/router, the function that does the remount.