Routing and rendering
Hum discovers routes from files. Special files add document shells, layouts, middleware, error pages, and not-found pages to the route tree.
Route files
Route files become URLs from their relative paths. This is the tree of the site you are reading: the guide pages come from docs/, and the page you are on now is the optional segment docs/partials/[[topic]].tsx. Parenthesized directories are transparent groups; bracket syntax marks dynamic, optional, and catch-all segments.
routes/
_app.tsx the document shell
_error.tsx route errors
_404.tsx unmatched paths
index.tsx /
docs/
_layout.tsx the guides sidebar
routing.tsx /docs/routing
handlers.tsx /docs/handlers
facets.tsx
streaming.tsx
partials/
[[topic]].tsx /docs/partials and /docs/partials/<topic>
_components/
CodeBlock.tsx view component, not a route
_islands/
Counter.tsx the island on the home page
Files in a route directory normally become routes. Put reusable view components in a nearby _components/ directory to keep them out of route discovery while still importing them with a relative path, for example import Author from "./_components/Author.js".
[id].tsxmatches one dynamic path segment.[[lang]].tsxis an optional single segment.[...slug].tsxmatches zero or more remaining segments.- Static routes take precedence over dynamic matches.
Layouts and special files
_app.tsx provides the outer document,_layout.tsx wraps routes in its directory,_middleware.ts mounts Hono middleware for routes below it,_error.tsx renders route errors, and_404.tsx handles unmatched paths in its directory subtree. More-specific layouts, middleware, and error/not-found pages apply within their route branch.
import { define } from "@humjs/core";
// routes/admin/_middleware.ts — a Hono middleware mounted for this directory
export default define.middleware(async (c, next) => {
const session = await getSession(c.req.raw);
if (!session) return c.redirect("/login");
c.set("user", session.user);
await next();
});Guarding an admin branch is the usual reason to add one, because the directory the file lives in is its scope. For app-wide or URL-prefix policy use app.use() in app.ts instead—the general alternative. This site has no _middleware.ts; the middleware it does run is Hono middleware in app.ts, covered in the next guide.
HTML first, islands by choice
Pages and components render to HTML on the server. Export a module-level defineIsland(Component) declaration from a browser-safe module to register an island. Hum generates its ID and client registration automatically; used islands hydrate in the browser. You can keep declarations in _components/, _islands/, or elsewhere in the project. Directory placement alone does not register a component.
// routes/_islands/Counter.tsx
import { defineIsland } from "@humjs/core/client";
import { useSignal } from "@preact/signals";
export default defineIsland(({ start = 0 }: { start?: number }) => {
const count = useSignal(start);
return <button onClick={() => count.value++}>{count}</button>;
});
// routes/index.tsx — the counter on the home page
import Counter from "./_islands/Counter.js";
<Counter start={3} />