Skip to content

Development and production

Warlock keeps one page graph for development SSR, browser hydration, and the production build. The same discovery rules decide which pages and layouts exist in every environment.

A component-body-only edit is left to React Fast Refresh. It updates the component without a document reload and preserves local state.

Anything outside the component body changes the module’s server-visible skeleton and forces a full reload. That includes:

  • imports and module-level declarations;
  • route, middleware, validation, loader, metadata, or prefix;
  • a module-level helper even when only the component calls it.

Reloading is deliberately conservative: it re-runs SSR and refreshes the document head instead of risking stale metadata.

Adding, deleting, or renaming a page, or changing its route identity, updates the live route table without restarting the development server. Warlock:

  1. atomically replaces the page-owned routes, restoring the previous table if registration fails;
  2. invalidates the browser page registry;
  3. sends a full document reload so the new server route and hydrated client graph agree.

When checking this manually, verify hydration with an interactive state change or intercepted <Link> navigation. A visible 200 proves only the server half.

If a request 404s while a matching *.page.tsx exists under src/web/ but is not registered, the development server logs a one-time warning naming that file.

Import global CSS directly from src/web/root.tsx:

src/web/root.tsx
import "./app.css";

A page or matched layout may import its own stylesheet. Warlock creates one ordered chain for the response: root, ancestor layouts, then page. Only the matched chain contributes render-blocking CSS.

In development, Warlock emits a source stylesheet link ending in ?direct, so Vite serves text/css instead of a JavaScript CSS module. The rendered root must contain a closing </head> because stylesheet links are inserted immediately before it.

warlock build uses the same discoverPages() scan and:

  • emits page-routes.manifest.json with each page’s method, path, name, and source file;
  • rejects page graphs that violate the one-rendering-layout rule;
  • builds the hydration client and a Vite manifest;
  • resolves root, matched-layout, and page CSS into ordered, deduplicated, render-blocking asset links;
  • stores the client asset directory in the page manifest so warlock start does not need to load the TypeScript build config.

At production boot, the Web connector refuses to start without the page manifest instead of reporting healthy while serving only 404s.

Use warlock routes:diff to compare the current diagnostic route table with the last successful build:

Terminal window
warlock routes:diff

The manifest records derived page names. A clean diff does not prove that no two routes collided during registration; discovery itself remains the source of that failure.

Dev SSR shares the app’s framework package instances

Section titled “Dev SSR shares the app’s framework package instances”

warlock dev runs SSR inside Vite’s module graph. Before 5.13, an app resolveAlias entry naming a framework package (@warlock.js/cache, core, logger, context, cascade) made Vite bypass ssr.external and load a second, never-booted copy of that package — a route with cache: { serverCache: true } then failed with CacheDriverNotInitializedError because the module graph’s copy never saw the connector’s initialization. Dev SSR now always imports those packages from the instance the app booted, as production does, and drops an app alias that would re-inline an SSR-external package.

A page request that fails outside the page pipeline itself — a cache failure, a module that fails to load — is now logged to stderr as [warlock:web] page request <METHOD> <path> failed: <error>. Before, the error reached only the app’s error.page.tsx, and the server log stayed empty.

  1. Confirm the root, current layout, or page directly imports the stylesheet.
  2. In development, inspect the rendered document for a link ending in ?direct and confirm it returns text/css.
  3. In production, confirm the matching source appears in client/.vite/manifest.json with CSS assets beneath the configured client prefix.
  4. Do not hand-author links to hashed production assets; the manifest owns their filenames.

See warlock routes:diff and connector lifecycle for the surrounding Core APIs.