Page setup files
Page logic belongs in .setup.ts; React components belong in the UI file.
A setup file is needed when a page has logic. A component-only page needs no
setup file.
| UI file | Setup file |
|---|---|
about.page.tsx | about.setup.ts |
products/[id].page.tsx | products/[id].setup.ts |
layout.tsx | layout.setup.ts |
root.tsx | root.setup.ts |
error.page.tsx | error.setup.ts |
404.page.tsx | 404.setup.ts |
forbidden.page.tsx | forbidden.setup.ts |
The UI file owns the default component and any named component such as
ErrorBoundary. Setup files own config, loader, register, and page
action or actions. Metadata always belongs in config.metadata. Each role keeps its own config type:
PageConfig, LayoutConfig, or RootConfig. Actions belong to ordinary pages;
error and not-found pages retain their special rendering contracts.
A complete pair
Section titled “A complete pair”import { groupedTranslations } from "@mongez/localization";import type { PageConfig } from "@warlock.js/web";
export async function loader() { return { teamName: "Acme" };}
export const config = { route: { path: "/about", name: "about" }, metadata: ({ data }) => ({ title: `About ${data.teamName}`, description: "Meet our team.", }),} as const satisfies PageConfig<typeof loader>;
export function register() { groupedTranslations({ team: { en: "Our team", ar: "فريقنا" } });}import { useTrans, type PageProps } from "@warlock.js/web";import type { loader } from "./about.setup";
export default function AboutPage({ data }: PageProps<typeof loader>) { const trans = useTrans(); return ( <h1> {trans("team")}: {data.teamName} </h1> );}import type { loader } from "./about.setup" retains
PageProps<typeof loader> inference without adding setup to the UI runtime
graph. Use type-only imports for action inference too. Do not value-import or
dynamically import setup from UI code.
Server and browser work
Section titled “Server and browser work”Loaders, actions, metadata callbacks, and config policy run on the server.
Metadata may be a static object or a function of loader data. Resolved metadata
reaches the server-rendered head, hydration payload, and client navigation.
Layout and root metadata compose with page metadata. Use config.metadata
for both the static object and function forms; PageConfig<typeof loader>
checks the callback and infers its data.
register() still runs in both realms, once per module namespace before
middleware and loaders. It is synchronous and must not return a Promise. The
framework projects browser-safe registration code and its dependencies to the
client. Keep registration safe for both realms; server-only services belong
in loaders or actions.
Browser-reachable API routes
Section titled “Browser-reachable API routes”A page document embeds only the named API routes its client code can reach, so
a page never carries the whole API surface. Warlock finds them from the page’s
browser code: a route name written as a string literal (apiHref("orders.show"),
useSubmitForm({ route: "orders.create" }), a constant), and the static prefix
of a template (`orders.${kind}` reaches every name under orders.). Only
registered routes qualify, and a route registered with clientVisible: false
never does.
A name built at run time that the build cannot see is declared in the setup
file’s config. Each entry is a route name or a prefix.* pattern:
export const config: PageConfig = { apiRoutes: ["orders.show", "reports.*"],};A layout or root config accepts the same key, and its names reach every page
that renders that layout. warlock check reports a route call that takes a name
it cannot read as WL9019 until a declaration covers it.
Duplicate declarations and development
Section titled “Duplicate declarations and development”Every logic export has one owner. Duplicates fail with the export name and source files; the framework never silently chooses a winner. Default components and named component exports belong in the UI file.
warlock dev picks up setup-file additions, edits, and deletions. Route changes
update the route graph; setup edits refresh server logic and its client
registration projection. Component-only edits remain eligible for Fast Refresh.