Dynamic Pages
By default every route is pre-rendered to a static HTML file at startup. For routes whose content must be generated per-request — because it depends on a query parameter, a session cookie, a database that changes between requests, or any other runtime data — you can opt in to dynamic rendering.
Opting in
Export isDynamic = true from any page.ts or layout.ts:
// pages/dashboard/page.ts
export const isDynamic = true;
export default function Page() {
return main(
h1("Dashboard"),
p("This is rendered fresh on every request."),
);
}Dynamicality
If any file in a route's chain (the page itself or any of its layouts) exports isDynamic = true, the entire route becomes dynamic.
This is done intentionally to ensure consistent behavior for any dynamic page (they are guaranteed access to req and res)
This means that a root layout that is dynamic means that every page in the app will be rendered per request. It's generally good to only use isDynamic when you require per-request information, and only use it whenever it's truly needed.
Slug Access
Dynamic pages can access slug parameters if their given route is a route containing slug segments.
They're simply passed into the page function.
export const isDynamic = true;
export default async function Page({ id }: { id: string }) {
const user = await db.users.findById(id);
return div(
h1(user.name),
p(user.bio),
);
}For query strings or headers you can read directly from reg, as it is passed to dynamic pages.
Enumerating static paths from a dynamic route
If a route has dynamic URL segments but its set of valid values is known ahead of time, you can get the best of both worlds: export a getEnumeratedRoutes function, which will build one static HTML file per route at startup, bypassing per-request rendering entirely.
// pages/blog/[slug]/page.ts
export const isDynamic = true;
export async function getEnumeratedRoutes(): Promise<string[]> {
const posts = await db.posts.findAll();
return posts.map(p => p.slug); // e.g. ["hello-world", "second-post"]
}
export default async function Page({ slug }: { slug: string }) {
const post = await db.posts.findBySlug(slug);
return article(
h1(post.title),
rawHTML(post.html),
);
}Dynamic layouts
Marking a layout as dynamic makes the entire subtree dynamic, even if the individual page files do not export isDynamic:
// pages/admin/layout.ts
export const isDynamic = true;
export default function AdminLayout({ child }: { child: () => VirtualNode }) {
// auth checks, session data, etc.
return div({ class: "admin" }, child());
}This is useful when a whole section of the site depends on runtime context (e.g. authentication) while the leaf pages themselves are straightforward.
However, you should avoid doing this in the root layout, as this will heavily hurt your optimizations.