Skip to content
Rendering
FrameworkRendering
On this page

Rendering

Send useful HTML first, then hydrate the page for interaction and navigation.

First visit and navigation

On a direct visit to a Worker-served page, Goribu runs middleware and the GET handler, resolves metadata, and streams HTML through the Document. A handler's res.render(props) supplies the props used by the page and its browser hydration.

After hydration, eligible Link transitions request a JSON description of the next page and its data. Goribu loads the page module and its styles, updates metadata, and commits the navigation. You keep writing ordinary React components and handlers.

Pre-rendered pages receive their initial HTML from Static Assets. They still hydrate. See Routes for the conditions that make a page static.

Page metadata

Export meta from a route. Use an object for fixed values:

src/routes/about.jsx
export const meta = {
  title: "About our team",
  description: "Meet the people building this app.",
  canonical: "https://example.com/about",
  robots: "index,follow",
};

// Page content lives inside the shared Document.
export default function AboutPage() {
  return <main><h1>About our team</h1></main>;
}

Use a synchronous function when metadata depends on page props or the URL. Load data in the handler, then pass it through res.render():

Dynamic metadata
// Add this export to a route that renders a post prop.
export function meta({ props: { post }, url }) {
  return {
    title: post.title,
    description: post.summary,
    canonical: new URL(url.pathname, "https://example.com").href,
    meta: [{ property: "og:type", content: "article" }],
  };
}

The function receives { props, url }. Do not make it async. Metadata is validated before streaming, and route-owned head tags are replaced on navigation. Shared tags such as the viewport and favicon stay in the Document.

Supported fields are title, description, canonical, robots, meta, and links. Use meta for extra name/property tags and links for additional link tags. Do not repeat a canonical or description in both representations. See the reference.

The Document

The starter supplies a Document. To customize it, edit src/routes/_document.tsx or _document.jsx:

src/routes/_document.jsx
import { ClientEntry, Stylesheet } from "goribu";

// Preserve the framework's head, hydration root, and browser entry.
export default function Document({ children, head, nonce }) {
  return (
    <html lang="en">
      <head>
        <meta charSet="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <Stylesheet />
        {head}
      </head>
      <body>
        <div id="root">{children}</div>
        <ClientEntry src="/src/client-entry.js" />
      </body>
    </html>
  );
}

Keep one Stylesheet, one ClientEntry, and the fixed #root. The entry's src is the development fallback; production resolves the hashed asset. Put page titles in meta, not in a competing Document title.

nonce is available for your own inline scripts on Worker-rendered responses. Pass it to those scripts when using a nonce-based policy. Static documents do not have per-request nonces. Worker response headers are configured in Configuration.

Styling and assets

The starter includes Tailwind. Plain CSS works too: import it from src/client-entry.js, a page, or a component. Goribu includes styles needed for the first render and loads the next page's styles during navigation.

src/client-entry.js
import "goribu/client";
import "./styles.css";

Use complete Tailwind class names so the scanner can find them. For example, select between "bg-red-500" and "bg-blue-500" instead of constructing a class from fragments.

Put files that need a fixed URL in public/: public/logo.svg is available at /logo.svg. Vite-managed imports are built into the asset graph. Do not create files under /_goribu/ or public HTML that shadows a route.

Server and browser code

Page components run in both environments. Read browser-only APIs in effects or event handlers, and keep request-specific work in handlers. Hydration expects the first browser render to match the HTML.

Anything serialized into page props is public. Keep credentials and private SDKs in server-only modules, and return only the fields the UI needs.