Custom Pages
Add landing pages, API routes and other pages outside your content.
For a landing page or other custom UI, add a file under src/pages. It uses Waku's routing, and takes priority over a plugin page at the same path.
Creating a Page
export default function Page() {
return (
<main>
<h1>About</h1>
<p>Built with Fumapress.</p>
</main>
);
}It is available at /about. Custom pages are React Server Components, and they are prerendered by default.
Don't give a custom page the same URL as a content page. To leave / free, put your content under
a baseDir.
Page Data
data in getConfig() is the page data of the page. Fumapress renders its title and description in <head>, and plugins read the rest, like ogImage for Open Graph images:
import type { WakuRouteConfig } from "fumapress";
export function getConfig() {
return {
data: { title: "About", description: "Who builds Acme." },
} satisfies WakuRouteConfig;
}On-Demand Rendering
Set render in getConfig():
import type { WakuRouteConfig } from "fumapress";
export function getConfig() {
return { render: "dynamic" } satisfies WakuRouteConfig;
}Layouts
_layout.tsx wraps the pages in its folder. Show your site's navbar with the Home Layout:
import type { ReactNode } from "react";
import { HomeLayout } from "fumadocs-ui/layouts/home";
import { getPressContext, type WakuRouteProps } from "fumapress";
import type { app } from "../../press.config";
export default async function Layout({
lang,
children,
}: WakuRouteProps<typeof app> & { children: ReactNode }) {
const layoutProps = await getPressContext<typeof app>().getLayoutProps({ lang });
// the Home Layout takes these props, the others are for docs layouts
const { nav, links, githubUrl, i18n, themeSwitch, searchToggle } = layoutProps;
const home = { nav, links, githubUrl, i18n, themeSwitch, searchToggle };
return <HomeLayout {...home}>{children}</HomeLayout>;
}links has the links of ui.layoutProps and plugins. To add links to this layout only, put them in front, like links={[...homeLinks, ...links]}. A links prop without them replaces them.
Dynamic Routes
import type { WakuRouteConfig, WakuRouteProps } from "fumapress";
import type { app } from "../../../press.config";
const items: Record<string, { title: string; description: string }> = {
acme: { title: "Acme", description: "Docs of Acme" },
};
export function getConfig() {
return {
render: "static",
staticPaths: Object.keys(items),
data: { title: "Showcase" },
} satisfies WakuRouteConfig;
}
type Props = WakuRouteProps<typeof app, "/showcase/[slug]">;
export default function Showcase({ slug }: Props) {
const item = items[slug];
if (!item) return null;
return (
<main>
<h1>{item.title}</h1>
{item.description}
</main>
);
}data is the same for every path of the route. To give each path its own title, return it from head, which receives the path.
404 Page
export default function NotFound() {
return <main>This page doesn't exist.</main>;
}Without this file, Fumapress renders the not found page of Fumadocs UI.
With internationalization, the built-in page uses the language of the URL when a server renders it. In static builds, 404.html is in the default language.
Reading Content
getPressContext() gives you the loader and helpers of your site. To get its types, export app from your config:
export const app = createPress(content).plugins(/* ... */);
export default app.config({/* ... */});See the Loader API for its other methods. If you are coming from Fumadocs, getPressContext() replaces your lib/source.ts.
Navigation
Import the client helpers from fumapress/client:
"use client";
import { Link, useRouter } from "fumapress/client";
export function Nav() {
const router = useRouter();
return (
<nav>
<Link href="/about">About</Link>
<p>{router.path}</p>
<button type="button" onClick={() => router.push("/docs", { scroll: false })}>
Docs
</button>
</nav>
);
}Use Link rather than <a>, so that link validation can check it.
The router also has replace(), reload(), back(), forward() and prefetch(), and the query and hash of the URL. Inside a Link, useNavigationStatus() returns pending while its navigation loads.
In a server component, redirect or show the not found page with fumapress/router:
import { notFound, redirect } from "fumapress/router";
export default function Page() {
if (!isSignedIn()) redirect("/login");
if (!hasAccess()) notFound();
return <main>Secret</main>;
}API Routes
export async function GET() {
return Response.json({ version: "1.0.0" });
}The path under _api is the URL, so this one is /version. Name each handler after an HTTP method, or use a default export for all methods.
API routes render on demand by default, and handlers can read getPressContext(). To generate a file at build time, use a single GET handler:
export function getConfig() {
return { render: "static" };
}
export async function GET() {
return Response.json({ version: "1.0.0" });
}Feature Support
Some features of content pages work differently on custom pages:
Last updated on