Authentication
Restrict your site or a section of it with middleware.
Fumapress has no built-in login. You check the session in plugin middleware, which runs first on every request your server receives.
Vercel, Netlify and Cloudflare serve prerendered pages and the files of public/ without your server, so those requests never reach middleware. Render your content on demand to put it behind middleware.
Protecting the Whole Site
Render Content on Demand
Set mode to force-dynamic. Pages of every source render on demand, and so do the files generated from them, like /llms.txt and the search index.
createPress(content, { mode: "force-dynamic" }); Custom pages are prerendered by default, so set render: "dynamic" in the getConfig() of each one you protect.
Add the Check
getSession() stands for your own session check. Add this plugin first, so that its middleware wraps the others.
createPress(content).plugins({
name: "site:auth",
async middleware(request, next) {
const session = await getSession(request);
if (!session) return new Response("Unauthorized", { status: 401 });
return next();
},
});Now every page, and every file generated from your content, like /llms.txt, requires a session.
Protecting a Section
Follow the first step of Protecting the Whole Site, then check in your middleware that the URL starts with the prefix. This check also covers client navigations and <page>.md.
createPress(content).plugins({
name: "site:auth",
async middleware(request, next) {
const { pathname } = new URL(request.url);
const session = await getSession(request);
const isInternal =
pathname.startsWith(this.withBase("/internal")) || pathname === this.withBase("/internal.md");
if (isInternal && !session) return new Response("Unauthorized", { status: 401 });
return next();
},
});With internationalization, the language prefix comes first, like /zh/internal.
URLs like //internal and /%69nternal redirect to /internal first, so your check only receives /internal.
Generated Routes
Plugins also serve your content at paths outside the prefix. For each route below, check its path in the same middleware, exclude the section with the options of the plugin, or remove the plugin.
| Route | Plugin |
|---|---|
/llms.txt, /llms-full.txt | llms.txt |
/api/search | Search |
/_takumi/... | Open Graph images |
/sitemap.xml | Sitemap |
/rss.xml | RSS |
/api/ai, /mcp | AI chat, MCP |
Hiding Pages from the Sidebar
Middleware blocks a page, but the sidebar still shows its link. Remove the folder from the page tree, the data behind the sidebar:
createPress(content, {
pageTree: {
transformers: [
{
root(node) {
return {
...node,
children: node.children.filter((item) => item.name !== "Internal"),
};
},
},
],
},
});The tree is the same for every reader, so the section is hidden from everyone, signed in or not.
Last updated on