Docs/Dynamic Pages
DYNAMIC PAGES

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:

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.

ts
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 currently read from Node's IncomingMessage via an API route or middleware (see API Routes and Middleware).

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.

ts
// 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), ); }

Enumerated routes are most often to make a slug route static by setting isDynamic to false, however, it may be useful to note that enumerated routes is allowed for dynamic pages.

Slugs that are not in the enumerated list still thus hit the server as live dynamic renders.

Dynamic layouts

Marking a layout as dynamic makes the entire subtree dynamic, even if the individual page files do not export isDynamic:

ts
// 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.