Router

This page is for site authors using qip router and qip dev.

It explains:

Project Layout

Typical setup:

site/                         # content root
  index.md

docs/                         # optional additional content root
  index.md
  how-it-works.md
  module-contract.md

recipes/                      # optional recipe root
  text/markdown/10-...wasm
  text/markdown/80-...wasm
  application/warc/10-...wasm

modules/                      # optional browser/module asset root
  utf8/trim.wasm
  application/warc/warc-check-broken-links.wasm

modules/form/                 # optional form module root
  contact.wasm

Related references:

Content Roots: Local or GitHub

<content_dir> can be either:

For GitHub roots, qip uses a two-step flow:

  1. Fetch repo HEAD SHA (git info/refs style endpoint).
  2. Fetch file bytes from GitHub raw CDN pinned to that SHA.

That means each runtime load is immutable once resolved: all content in that run comes from one commit SHA.

Examples:

qip dev github:cool-calm/collected-press/site --recipes ./recipes --modules ./modules
qip router get github:cool-calm/collected-press/site /docs/how-it-works --recipes ./recipes
qip router warc github:cool-calm/collected-press/site --recipes ./recipes > site.warc

Route Behavior

Given a content file like site/docs/module-contract.md, qip routes:

Common behavior:

Redirect Files (.uri / .uris)

We prefer file-based redirects because they are explicit, versioned, and easy to review.

If a content file ends with .uri or .uris, qip serves that route as 302 Found using the first redirect target from a text/uri-list payload.

Example:

# site/how-it-works.uri
/docs/how-it-works

This gives you:

What Runs When

qip dev <content_dir> ...

Per request:

  1. Resolve request path to a content file or module asset.
  2. For content pages, run matching content recipes (for example recipes/text/markdown/*).
  3. If response is HTML, inject runtime support for <qip-form>, <qip-preview>, and <qip-play>.
  4. If recipes/application/warc/* exists, apply that WARC recipe layer to this page response too.
  5. Serve the final response.

Result: preview in dev matches route-archive behavior for WARC-level transforms.

qip router get <content_dir> <path> ...

Runs the same page pipeline as dev for one path and prints the response body.

qip router head <content_dir> <path> ...

Runs the same route pipeline but returns headers only.

qip router list <content_dir> ...

Lists all routed paths and content types.

qip router warc <content_dir> ...

  1. Enumerate all routed paths.
  2. Resolve each path to a response.
  3. Build one WARC archive from those responses.
  4. If recipes/application/warc/* exists, run it over the archive.
  5. Emit final WARC bytes.

If --view-source is used, source artifacts are added as extra WARC records.

Recipe Layers

There are two independent recipe layers:

Practical Build Pattern

Static export usually looks like:

  1. qip router warc ./site --recipes recipes --forms modules/form --modules modules
  2. pipe WARC into archive-processing modules (for checks or export)

Example pipeline:

qip router warc ./site --recipes recipes --forms modules/form --modules modules \
  | qip run modules/application/warc/warc-check-broken-links.wasm \
           modules/application/warc/warc-to-static-tar-no-trailing-slash.wasm

In this model: