--- slug: create-pages title: createPages description: The low-level routing API. --- ## Routing (low-level API) The entry point for programmatic routing in Waku projects is `./src/waku.server.tsx`. In that file, call `createPages(...)` and pass the result to an adapter. `createPage`, `createLayout`, `createRoot`, `createSlice`, and `createApi` register programmatic routes and renderable entries. Pages, layouts, roots, and slices specify a React component and render method. APIs specify HTTP handlers. Waku currently supports two render options: `'static'` for static prerendering (SSG) or `'dynamic'` for server-side rendering (SSR). For example, you can statically prerender a global header and footer in the root layout at build time, but dynamically render the rest of a home page at request time for personalized user experiences. ```tsx // ./src/waku.server.tsx import { createPages } from 'waku'; import adapter from 'waku/adapters/default'; import { RootLayout } from './templates/root-layout'; import { HomePage } from './templates/home-page'; import { Root } from './components/root'; import { Slice } from './components/slice'; const pages = createPages( async ({ createPage, createLayout, createRoot, createSlice, createApi }) => [ // Create root component // not required, but supported for customizing // ``, ``, and `` tags createRoot({ render: 'static', component: Root, }), // Create root layout createLayout({ render: 'static', path: '/', component: RootLayout, }), // Create home page createPage({ render: 'dynamic', path: '/', component: HomePage, }), // Create slice createSlice({ render: 'static', component: Slice, id: 'slice-1', }), // Create API endpoint createApi({ render: 'static', path: '/health', method: 'GET', handler: async () => new Response('OK'), }), ], ); export default adapter(pages); ``` ### Pages #### Single routes Pages can be rendered as a single route (e.g., `/about`). ```tsx // ./src/waku.server.tsx import { createPages } from 'waku'; import adapter from 'waku/adapters/default'; import { AboutPage } from './templates/about-page'; import { BlogIndexPage } from './templates/blog-index-page'; const pages = createPages(async ({ createPage }) => [ // Create about page createPage({ render: 'static', path: '/about', component: AboutPage, }), // Create blog index page createPage({ render: 'static', path: '/blog', component: BlogIndexPage, }), ]); export default adapter(pages); ``` #### Segment routes Pages can also render a segment route (e.g., `/blog/[slug]`). The rendered React component automatically receives a prop named by the segment (e.g, `slug`) with the value of the rendered segment (e.g., `'introducing-waku'`). If statically prerendering a segment route at build time, a `staticPaths` array must also be provided. **Note:** When using `staticPaths`, spaces in slug values are sanitized to `-`. ```tsx // ./src/waku.server.tsx import { createPages } from 'waku'; import adapter from 'waku/adapters/default'; import { BlogArticlePage } from './templates/blog-article-page'; import { ProductCategoryPage } from './templates/product-category-page'; const pages = createPages(async ({ createPage }) => [ // Create blog article pages // `` receives `slug` prop createPage({ render: 'static', path: '/blog/[slug]', staticPaths: ['introducing-waku', 'introducing-create-pages'], component: BlogArticlePage, }), // Create product category pages // `` receives `category` prop createPage({ render: 'dynamic', path: '/shop/[category]', component: ProductCategoryPage, }), ]); export default adapter(pages); ``` Static paths (or other values) could also be generated programmatically. ```tsx // ./src/waku.server.tsx import { createPages } from 'waku'; import adapter from 'waku/adapters/default'; import { getBlogPaths } from './lib/get-blog-paths'; import { BlogArticlePage } from './templates/blog-article-page'; const pages = createPages(async ({ createPage }) => { const blogPaths = await getBlogPaths(); return [ createPage({ render: 'static', path: '/blog/[slug]', staticPaths: blogPaths, component: BlogArticlePage, }), ]; }); export default adapter(pages); ``` #### Nested segment routes Routes can contain multiple segments (e.g., `/shop/[category]/[product]`). ```tsx // ./src/waku.server.tsx import { createPages } from 'waku'; import adapter from 'waku/adapters/default'; import { ProductDetailPage } from './templates/product-detail-page'; const pages = createPages(async ({ createPage }) => [ // Create product detail pages // `` receives `category` and `product` props createPage({ render: 'dynamic', path: '/shop/[category]/[product]', component: ProductDetailPage, }), ]); export default adapter(pages); ``` For static prerendering of nested segment routes, the `staticPaths` array is instead composed of ordered arrays. ```tsx // ./src/waku.server.tsx import { createPages } from 'waku'; import adapter from 'waku/adapters/default'; import { ProductDetailPage } from './templates/product-detail-page'; const pages = createPages(async ({ createPage }) => [ // Create product detail pages // `` receives `category` and `product` props createPage({ render: 'static', path: '/shop/[category]/[product]', staticPaths: [ ['some-category', 'some-product'], ['some-category', 'another-product'], ], component: ProductDetailPage, }), ]); export default adapter(pages); ``` #### Catch-all routes Catch-all or "wildcard" routes (e.g., `/app/[...catchAll]`) have indefinite segments. Wildcard routes receive a prop with segment values as an ordered array. For example, the `/app/profile/settings` route would receive a `catchAll` prop with the value `['profile', 'settings']`. These values can then be used to determine what to render in the component. ```tsx // ./src/waku.server.tsx import { createPages } from 'waku'; import adapter from 'waku/adapters/default'; import { DashboardPage } from './templates/dashboard-page'; const pages = createPages(async ({ createPage }) => [ // Create account dashboard // `` receives `catchAll` prop (string[]) createPage({ render: 'dynamic', path: '/app/[...catchAll]', component: DashboardPage, }), ]); export default adapter(pages); ``` ### Advanced page options #### Disabling SSR `unstable_disableSSR` disables document SSR for a page. Runtime document requests return Waku's fallback HTML shell, and the build writes that shell for a static page. The RSC payload is still available to the client router. ```tsx // ./src/waku.server.tsx import { createPages } from 'waku'; import adapter from 'waku/adapters/default'; import { AppPage } from './templates/app-page'; const pages = createPages(async ({ createPage }) => [ createPage({ render: 'static', path: '/app', component: AppPage, unstable_disableSSR: true, }), ]); export default adapter(pages); ``` This option is unstable and may change. #### Exact paths `exactPath: true` treats the path string literally. Slug and wildcard tokens such as `[slug]` or `[...path]` are matched as literal path segments instead of dynamic route patterns. ```tsx createPage({ render: 'static', path: '/legacy/[literal]', exactPath: true, component: LegacyPage, }); ``` This is primarily intended for custom router integrations. ### Router paths type safety Waku provides inference for the router paths when created pages are returned from the callback passed into `createPages`. The following example shows how to setup router paths type safety. ```tsx // ./src/waku.server.tsx import { createPages } from 'waku'; import adapter from 'waku/adapters/default'; import type { PathsForPages } from 'waku/router'; import { RootLayout } from './templates/root-layout'; import { HomePage } from './templates/home-page'; const pages = createPages(async ({ createPage, createLayout }) => [ createLayout({ render: 'static', path: '/', component: RootLayout, }), createPage({ render: 'dynamic', path: '/', component: HomePage, }), ]); declare module 'waku/router' { interface RouteConfig { paths: PathsForPages; } } export default adapter(pages); ``` Once this is done, string navigation targets use the inferred paths. In this case, the one valid use would be ``, but as you add more pages to the router, this type will grow to include them. For parameterized URLs, use `unstable_createRouteHref` from `waku/router`. Its factories infer params and search from a local pattern and optional codec. This augmentation checks raw string hrefs, not factory patterns: without generated route metadata, a factory for an unknown path is accepted by navigation. See [Typed Routes](/guides/typed-routes#what-the-types-check). #### PageProps The `PageProps` type derives the page path and slug parameter types. ```tsx // ./src/templates/about-page.tsx import type { PageProps } from 'waku/router'; // PageProps<'/about/[foo]'> => { path: `/about/${string}`; foo: string; query: string; } export const AboutPage = ({ foo }: PageProps<'/about/[foo]'>) => { return <>{/* ...*/}; }; ``` The route patterns passed to `PageProps`, `useParams_UNSTABLE`, and href factories are not checked against this manual route table. Do not add the following augmentation: ```tsx // ./src/waku.server.tsx declare module 'waku/router' { interface CreatePagesConfig { pages: typeof pages; } } ``` A manual `createPages` table contains the page components, so using `PageProps` in one imported module and `` in another creates a circular type dependency through that table. The file-system router can safely provide this augmentation because its generated type table contains only route metadata. ### Layouts Layouts wrap an entire route and its descendents. They must accept a `children` prop of type `ReactNode`. While not required, you will typically want at least a root layout. #### Root layout The root layout rendered at `path: '/'` is especially useful. It can be used for setting global styles, global metadata, global providers, global data, and global components, such as a header and footer. ```tsx // ./src/waku.server.tsx import { createPages } from 'waku'; import adapter from 'waku/adapters/default'; import { RootLayout } from './templates/root-layout'; const pages = createPages(async ({ createLayout }) => [ // Add a global header and footer createLayout({ render: 'static', path: '/', component: RootLayout, }), ]); export default adapter(pages); ``` ```tsx // ./src/templates/root-layout.tsx import '../styles.css'; import { Providers } from '../components/providers'; import { Header } from '../components/header'; import { Footer } from '../components/footer'; export const RootLayout = async ({ children }) => { return (
{children}