← Back
ygwyg

ygwyg/ry

ry stands for route yes: an AI router for 404s, powered by Cloudflare Clef

View on GitHub ↗https://route-yes-example.burcs.workers.dev ↗
astroclefcloudflareviteworkers-ai
Stars
3
Forks
2
Watchers
3
Open issues
1
Contributors
1
Language
TypeScript
License
—
Default branch
main
Created Oct 1, 2026Updated Oct 1, 2026

Star growth

Today—
This week—
This month—

Star history will appear here once this repo has been tracked for a couple of days.

README

ry, a pixel-art Clefairy
clef•ai•ry

ry

ry stands for route yes.
An AI router for 404s, built on Cloudflare Clef.

Try the live demo

Clicking broken links on the demo site: ry sends /priceing to Pricing and /docs/quickstart to Getting started, and offers suggestions for /our-team

When someone lands on a page that doesn't exist, ry asks Clef which real page they meant and sends them there.

/priceing            → /pricing/                typo
/docs/quickstart     → /docs/getting-started/   renamed page
/release-notes       → /changelog/              synonym
/About/              → /about/                  fixed without AI
/our-team            → "did you mean About or Careers?"
/wp-admin/setup.php  → 404                      bot probe, ignored

Why Clef

Clef is a decision model, not a chatbot. You hand it a list of options and it returns a probability for each one. That makes it a good fit here:

  • It can't make up a URL. Your pages are the options, so Clef can only pick one of them, or "none".
  • The probabilities mean something. ry redirects when Clef is sure, suggests pages when it's torn, and leaves the 404 alone otherwise.
  • One call does two jobs. The same request asks whether the visitor looks like a person or a bot scanning for /wp-admin.

Quick start

npm i route-yes

1. List your pages at build time. This writes route-yes.json with every page's path, title, and description.

Framework Add
Vite (React, Vue, Svelte, TanStack Router, React Router…) plugins: [routeYes()] from route-yes/vite
Astro integrations: [routeYes()] from route-yes/astro
Anything else npx route-yes manifest dist after your build

The Vite plugin reads built HTML, sitemap*.xml, and TanStack's routeTree.gen.ts. Add pages it can't see with routeYes({ routes: ["/pricing"] }).

2. Route 404s in your Worker.

import { withRouteYes } from "route-yes/worker";

// Static site: serves from ASSETS and routes the misses
export default withRouteYes();

// SSR framework: wrap its handler instead
// export default withRouteYes(handler);

3. Add the bindings.

// wrangler.jsonc
{
  "assets": {
    "directory": "./dist",
    "binding": "ASSETS",
    "not_found_handling": "none"
  },
  "ai": { "binding": "AI" },
  // Optional: caches decisions so repeat visits skip Clef
  "kv_namespaces": [
    { "binding": "ROUTE_YES_CACHE", "id": "…" }
  ]
}

Important

not_found_handling must be "none". With 404-page or single-page-application, Cloudflare answers missing pages before your Worker runs. ry still serves your 404.html when it doesn't redirect.

Single-page apps

In a single-page app the 404 happens in the browser, so your not-found view asks the Worker:

import { routeYes } from "route-yes/client";

// e.g. in TanStack Router's notFoundComponent
const decision = await routeYes({ navigate: (to) => navigate({ to, replace: true }) });
// No confident match? Show decision.suggestions instead.

Add "run_worker_first": ["/_route-yes/*"] to assets so those requests reach the Worker.

How ry decides

  1. Fix the easy ones. Differences in case, slashes, .html, or /index get a 301.
  2. Skip the noise. Asset requests and obvious probes (.env, .php, wp-admin) keep their 404.
  3. Fix typos. A URL one or two keystrokes from exactly one page redirects immediately.
  4. Shortlist. On sites with more than 60 pages, ry picks the 60 likeliest by spelling and by meaning (Workers AI embeddings), so /jobs still finds /careers.
  5. Ask Clef which page the visitor meant (or none), and whether they look like a person.
  6. Ask again if unsure. If Clef's top pick is weak, ry asks a second time with only its top 5. With fewer options it can commit.
  7. Act. ry sends a 302 when the top page scores 0.7 or higher, or 0.5 and three times the runner-up. Weaker matches get a "did you mean" page. The query string is kept on redirects.
  8. Cache each decision by path in memory, the Cache API, and KV. Redeploying with changed pages clears it.

Steps 1 to 3 don't call AI. On a 662-page test site, ry sends 76% of broken URLs to the right page, and never to the wrong one. Including suggestions, it offers the right page 97% of the time. See eval/RESULTS.md.

Every response gets an x-route-yes header with the decision, the confidence, and how long Clef took. If anything fails, the visitor gets your normal 404.

Options

withRouteYes(handler, {
  model: "clef-flash",           // or "clef", the larger model
  minConfidence: 0.7,            // always redirect at or above this
  minDominantConfidence: 0.5,    // ...or above this when it's
  dominance: 3,                  //    3x likelier than the runner-up
  minHumanLikelihood: 0.5,       // below this, treat the visitor as a bot
  typoFix: true,                 // redirect obvious typos without calling Clef
  shortlist: "hybrid",           // or "lexical" (spelling only, no embedding calls)
  maxCandidates: 60,             // pages sent to Clef; more options dilute its confidence
  rerank: true,                  // ask again with the top 5 when unsure
  siteDescription: "…",          // one line about your site; helps with vague paths
  fallback: "suggest",           // or "passthrough" to always show your own 404
  renderMiss: (decision) => …,   // your own "did you mean" page
  cacheTtl: 86400,               // seconds; 0 turns caching off
  aiStatus: 302,                 // a model guessed, so not permanent by default
  onDecision: (decision) => …,   // logging or analytics
});

The resolver also works outside Workers, through the REST API:

CLOUDFLARE_ACCOUNT_ID=… CLOUDFLARE_API_TOKEN=… npx route-yes try /priceing

Good to know

  • Speed. During launch week, uncached Clef calls took 0.5 to 5 seconds, not the ~40ms Cloudflare advertises. Cached paths skip Clef, so bind KV. The Cache API doesn't work on *.workers.dev.
  • Borderline paths can flip. Confidence varies a little between calls, so a path near the threshold might redirect once and show suggestions another time. Whichever answer comes first is cached.
  • Your page copy matters. Clef reads your titles and descriptions, falling back to each page's first paragraph. A homepage description that mentions every section pulls guesses toward the homepage.
  • Big sites use embeddings. Above 60 pages, each uncached 404 also makes one small embedding call. Each Worker instance embeds the whole page list once (about 1s for 660 pages). Set shortlist: "lexical" to avoid both.
  • Cost. Clef only runs for page visits that 404, once per path per cache period. On a public site, add rate limiting so floods of random URLs can't run up your bill.
  • Safety and SEO. ry only redirects to pages in your manifest, so it can't be used as an open redirect. AI redirects are 302s, and the suggestions page is noindex.
  • Dynamic routes like /posts/:id aren't redirect targets. Prerendered pages and sitemap entries are.

Develop

pnpm install && pnpm test
cd examples/vite-site && pnpm dev   # runs the demo against real Clef