# Wraith Docs — server-side routing

Middleware calls POST /api/v1/route per request with
{site, path, user_agent, headers}. Wraith returns {classification,
session_id, rule, action}. Actions: serve (body + content_type),
redirect (url), webhook, none.

## Next.js (middleware.ts)
const WRAITH = "https://wraith-daemon-production.up.railway.app";
export const config = { matcher: ["/((?!_next|.*\\..*).*)"] };
export async function middleware(req) {
  const r = await fetch(`${WRAITH}/api/v1/route`, {
    method: "POST",
    headers: { "content-type": "application/json",
               "x-wraith-site-key": "wk_your-site-key" },
    body: JSON.stringify({ site: "yoursite", path: req.nextUrl.pathname,
      user_agent: req.headers.get("user-agent") ?? "",
      headers: Object.fromEntries(req.headers) }),
  });
  const { action } = await r.json();
  if (action.type === "serve")
    return new Response(action.body, { headers:
      { "content-type": action.content_type ?? "text/markdown" } });
  if (action.type === "redirect") return Response.redirect(action.url, 307);
  return NextResponse.next();
}

## Express
app.use(async (req, res, next) => {
  if (req.method !== "GET" || req.path.includes(".")) return next();
  try {
    const r = await fetch(`${WRAITH}/api/v1/route`, { method: "POST",
      headers: { "content-type": "application/json",
                 "x-wraith-site-key": "wk_your-site-key" },
      body: JSON.stringify({ site: "yoursite", path: req.path,
        user_agent: req.get("user-agent") ?? "", headers: req.headers }),
      signal: AbortSignal.timeout(2000) });
    const { action } = await r.json();
    if (action.type === "serve")
      return res.type(action.content_type ?? "text/markdown").send(action.body);
    if (action.type === "redirect") return res.redirect(307, action.url);
  } catch {}
  next(); // fail open — never block the site on routing
});

Then configure ordered intent rules in the dashboard Actions view:
{path_glob, applies_to, action, source{url|inline|redirect|webhook}}.
