radix3
A lightweight, dependency-free radix-tree router for JavaScript and TypeScript, matching static, parameterized, and wildcard routes with fast, precomputable lookups.
Repository Health
Technical Analysis
radix3 is a lightweight JavaScript router built on a radix tree (compressed prefix tree) data structure, giving it fast route insertion and lookup with a minimal memory footprint. It supports static paths, named parameters (:name), and wildcard segments (** and **:name), returning matched route data alongside any extracted params on lookup.
Beyond the core createRouter/insert/lookup/remove API, radix3 ships a toRouteMatcher utility that compiles a router’s tree into a flat lookup table capable of returning every route that matches a given path, not just the most specific one, ordered from least to most specific. This matcher can also be exported to a plain JSON object and rehydrated later, letting consumers precompute routing tables at build time and skip tree construction at runtime. It has no runtime dependencies and ships as dual ESM/CJS builds with TypeScript types, making it a drop-in routing core for frameworks and CLIs. It was originally based on charlieduong94/radix-router and is maintained by the UnJS collective.
What You Get
- A dependency-free
createRouter()factory withinsert,lookup, andremovemethods operating on a radix tree - Support for static,
:namedplaceholder, and**/**:namewildcard route segments with automatic precedence (static > placeholder > wildcard) - A
toRouteMatcher/matchAllAPI that returns every route matching a path, ordered least-to-most specific, useful for merging route-level config exportMatcher/createMatcherFromExportto serialize a compiled matcher to JSON and rehydrate it without rebuilding the tree- Dual ESM/CJS builds with bundled TypeScript types and zero runtime dependencies
Common Use Cases
- Routing engine inside server frameworks and meta-frameworks that need fast static and dynamic path matching
- CLI or build tools that need to match file paths against glob-like route patterns
- Config/rule systems where multiple overlapping patterns must all be resolved and merged for a given path via
toRouteMatcher - Precompiling routing tables at build time and shipping the exported JSON to avoid runtime tree construction in edge/serverless environments
Under The Hood
Architecture
radix3’s architecture centers on two independent modules that share only the RadixRouter tree shape defined in src/types.ts: src/router.ts implements the mutable radix tree itself (createRouter, insert, lookup, remove) using a RadixNode with a children Map, a placeholderChildren array, and a single wildcardChildNode, plus a staticRoutesMap fast-path cache for fully static routes; src/matcher.ts is a separate read-only projection (toRouteMatcher/_routerNodeToTable) that walks the same root node once to build a flattened route table of static/wildcard/dynamic maps, with its own independent matchAll algorithm rather than reusing lookup’s logic. This is a layered, single-purpose design (build tree, then optionally derive a matcher) with no external state; the whole library is a few hundred lines split across router.ts, matcher.ts, and types.ts, and because matcher.ts reads router internals directly (node.type, node.children, node.wildcardChildNode) rather than through an abstraction, a change to the core RadixNode shape would require both files to change in lockstep.
Tech Stack
radix3 has zero runtime dependencies; its devDependencies are entirely tooling: TypeScript for types, Vitest for tests, unbuild to produce dual ESM (dist/index.mjs) and CJS (dist/index.cjs) builds with bundled .d.ts types, ESLint (via eslint-config-unjs) and Prettier for linting/formatting, changelogen for release/changelog automation, and a small benchmark suite (0x, autocannon, benchmark, listhen) used to profile the router against an HTTP server. It targets any JavaScript runtime (Node, browsers, edge) since the source touches no platform-specific APIs.
Code Quality
Tests live in tests/router.test.ts and tests/matcher.test.ts, run under Vitest with inline snapshot assertions, and are substantial relative to the source size, covering the README’s own examples plus edge cases like trailing-slash handling, wildcard precedence, and static-route short-circuiting. Types are moderately strict for a library this size, including a params-reserved-key guard expressed at the type level. Error handling is minimal by design: misses return null/false rather than throwing. Linting runs through eslint-config-unjs, an opinionated shared config, with an autofix CI bot (autofix.ci) alongside a standard test/lint CI workflow.
API Design
radix3’s public surface is intentionally small: a single createRouter(options) factory returns an object with insert/lookup/remove methods operating on plain strings and data objects, so the entire onboarding path is install, import, insert, lookup, with no classes, decorators, or build step required. The one API wrinkle is the reserved params key on inserted data objects, enforced only at the type level, which can surface if a consumer’s own route data happens to use that name. The auxiliary toRouteMatcher/matchAll API is a separate entry point rather than a method on the router itself, so consumers who want “all matching routes” must learn a second construct with its own export/import round-trip (exportMatcher/createMatcherFromExport) — a natural fit for its build-time-precompute use case, though it gets comparatively brief README coverage next to the core API. Overall the ergonomics favor minimalism and zero ceremony over exhaustive configurability.
Used by 2 apps in this directory
Papra
Bookmarks Archiving
Self-hosted document archiving with email ingestion, OCR full-text search, and pluggable storage — store once, find anything.
Unlighthouse
Developer Tools
An open-source CLI that scans your entire website with Google Lighthouse, using smart sampling and a modern dashboard UI to audit every page instead of one URL at a time.