夯HumGuides
Back to home

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.

Shell
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].tsx matches one dynamic path segment.
  • [[lang]].tsx is an optional single segment.
  • [...slug].tsx matches 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.

TypeScript
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.

TypeScript
// 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} />

Next: Hono integration and deployment →