4. Pages and layouts
The list is one page. In this step each book gets a page of its own at /books/1, /books/2 and so on, and a layout puts a header on every page.
How files become URLs
Grimoire, Sigil's server framework, maps files under src/routes to URLs:
| File | URL |
|---|---|
src/routes/index.tsx | / |
src/routes/about.tsx | /about |
src/routes/books/+page.tsx | /books |
src/routes/books/[id]/+page.tsx | /books/1, /books/piranesi, … |
src/routes/+layout.tsx | wraps every page |
A folder is a URL segment. +page.tsx is the page for its folder; a plain file like about.tsx is a shortcut for about/+page.tsx. Square brackets make a segment a parameter. The full rules, including optional and catch-all segments, are in Route files.
A page with a parameter
Create src/routes/books/[id]/+page.tsx:
import { Head } from "@sigil-dev/grimoire";
const BookPage = ({ params }: { params: { id: string } }) => (
<article>
<Head>
<title>Book {params.id}</title>
</Head>
<h1>Book {params.id}</h1>
<p>
<a href="/">Back to the list</a>
</p>
</article>
);
export default BookPage;Every page component receives params, the values of its bracketed segments, always as strings. Visit /books/2 and you'll see "Book 2". The page doesn't know anything about the book yet; step 5 loads it.
<Head> puts its children into the document's <head>. Here it sets the tab title.
Link to it
In src/lib/BookRow.tsx, make the title a link:
<a class="title" href={`/books/${book.id}`}>
{book.title}
</a>Links are plain <a href>. Once the page has loaded, Grimoire's router handles clicks on links to your own pages: it fetches the new page's data, swaps the content, and updates the URL and title, without a full reload. Hovering a link fetches its data ahead of the click. The back button works as usual.
A layout
The scaffold already has src/routes/+layout.tsx. Replace it with a header that links home:
import { Head } from "@sigil-dev/grimoire";
const Layout = ({ children }: { children?: unknown }) => (
<>
<Head>
<title>Reading list</title>
</Head>
<link rel="stylesheet" href="/style.css" />
<header class="top">
<a href="/">Reading list</a>
<nav>
<a href="/about">About</a>
</nav>
</header>
<main>{children}</main>
</>
);
export default Layout;A layout wraps every page in its folder and below. children is where the page goes. The layout's <title> is a default: a page's own <Head> title wins.
Since the layout now provides <main>, change the page's outer <main> in src/routes/index.tsx to a fragment, <>…</>.
Layouts nest: a +layout.tsx in src/routes/books/ would wrap only the book pages, inside the root layout. On client-side navigation the layouts render again along with the page, so keep long-lived state out of them. Layouts has the details.
Next: loading data.