URL router#
This page covers how the file router scans the filesystem, builds URL patterns, and reloads at runtime.
Overview#
The URL subsystem owns the file router, the filesystem walker that discovers page modules, and the reverse helpers.
It listens on the Django URL resolver through include("next.urls") and produces patterns from the filesystem layout.
Pipeline#
flowchart TB
Walk[Filesystem walk] --> Parser[Parser]
Parser --> Patterns[URL patterns]
Patterns --> Manager[RouterManager]
Manager --> Trie[TrieURLResolver]
Resolver[Django URL resolver] --> Trie
Trie --> PageView[Page view]
Manager -- reload --> Reload[Rebuild patterns]
Reload --> Patterns
Modules#
next.urls.backends.RouterBackendis the abstract contract. Its concretepage_roots,components_folder_name, andskip_dir_namesmethods form the route introspection contract described below.FileRouterBackendimplements file based routing.RouterFactorylooks up backends by dotted path.next.urls.parser.Turns directory names into URL patterns. Recognises
[name],[type:name], and[[name]]shapes. Thedefault_url_parser.parse_url_patternmethod is the single source of bracket conversion, used by both the router and the system checks, so a route and its check always agree on the resulting Django path string.next.urls.manager.RouterManagerbuilds the active pattern list, exposesreload, and emits therouter_reloadedsignal. The module-levelurlpatternsis a list with oneTrieURLResolverwrapping the lazy router and form-action pattern sequence.next.urls.resolver.TrieURLResolvernarrows eachresolve()call to a few candidates through a static route map and a segment trie, with the inherited linear scan as fallback.next.urls.dispatcher.FilesystemTreeDispatcherwalks the pages directory tree and yields(url_path, page_file)pairs that the router turns into URL patterns. The module-level helperscan_pages_treeinstantiates the dispatcher with the configured skip set and returns the same pairs as an iterator. The walk itself isnext.utils.walk_page_tree, which sits below both this package and the checks layer because the system checks run the same walk over the trees a backend reports. The dispatcher adds the one thing the checks do not want, a callback on each skipped directory that registers the component folders the router passes.next.urls.markers.Hosts the
DUrlandDQueryannotation markers, the four request/URL/query parameter providers, and theget_multi_valueshelper. See Dependency injection for the marker semantics and the provider order.next.urls.reverse.page_reverse,page_reverse_lazy, andwith_queryhelpers.
URL name computation#
Names follow next:page_<segments> where the segments come from the directory path.
Static segments contribute their directory name unchanged.
Captured segments contribute the raw bracket text with separators collapsed to underscores, so a converter prefix such as
int:stays in the name.Wildcard segments contribute the parameter name without brackets.
The template URL_NAME_TEMPLATE controls the format.
The default page_{name} produces the names listed in File router.
The name computation collapses /, brackets, :, -, and _ into a single underscore, so distinct routes such as foo-bar and foo_bar can produce the same name.
The check_reverse_name_collisions system check walks every page tree of every router, computes the reverse name of each route through the same parser, and fails with next.E039 when two distinct routes collapse to one name, listing the conflicting paths.
Resolution algorithm#
include("next.urls") mounts a single TrieURLResolver that wraps the lazy router and form-action pattern sequence.
The sequence caches the concatenated pattern list against a pair of version counters, one bumped by router_manager.reload() and one bumped when form actions register or clear through the form-action manager.
The counters are read after the pattern build, because expanding page modules can register form actions mid-build, so the cache stays valid for the post-registration state.
A registration that writes into a backend directly, bypassing the manager, is not tracked.
The resolver builds a route index from the same sequence and versions it with the same counter pair.
A route without parameters lands in a static map keyed by the full route string, so a static hit is one dictionary lookup.
A parameterised route becomes a path through a segment trie. A literal segment becomes a literal edge, and a segment whose converters all match within one segment,
int,str,slug, oruuid, becomes a param edge.A segment that can span slashes, such as
<path:...>or a custom converter, stops indexing at its node, and the candidate matches any remaining tail from there.A pattern that is not a route-string pattern, such as one built with
re_path(), is never indexed and joins the candidate list of every lookup.
resolve() collects candidates from the static map and a depth-first walk over the trie, following the literal edge before the param edge at every node and picking up tail candidates along the way.
The candidates are sorted by their original position in the pattern list and tried with the standard pattern.resolve(), so overlapping routes keep Django’s first-match-wins rule and converters run unchanged.
When no candidate matches, the resolver falls back to the inherited linear scan, which raises the canonical Resolver404 with the complete tried list.
The fallback also covers any pattern the trie could not index.
On a successful match ResolverMatch.tried contains only the candidates that were actually tried, not every pattern that precedes the winner in the list.
A 404 goes through the fallback, so its tried is identical to the linear scan.
Setting URL_RESOLVER in NEXT_FRAMEWORK to "django.urls.resolvers.URLResolver" replaces the trie resolver with the stock Django class and routes every call through the plain linear scan.
The resolver is rebuilt on settings reload, so the swap takes effect without a restart.
Reload mechanics#
router_manager.reload() does four things in order.
Bumps the version counter that invalidates the cached pattern concat and the resolver’s route index.
Rebuilds the backend list from
PAGE_BACKENDS.Clears the Django URL resolver cache.
Emits the
router_reloadedsignal.
The next request observes the new patterns without a process restart. Long lived processes such as websocket subscribers listen for the signal to refresh cached URL references.
Multiple backends#
The settings list accepts more than one backend. Each backend reports its own list of patterns. Resolution preserves the concatenated list order, so the first match wins.
If two routes convert to exactly the same Django path string the check_url_patterns system check reports the conflict at startup as next.E015, whether they come from one tree or several.
The comparison is string equality after bracket conversion, so semantic overlap between typed converters, such as posts/[int:id] next to posts/[id], is not reported.
The same collection pass owns the duplicate parameter report, failing with next.E028 and listing every conflicting name in one message, and reports a router whose collection raises as next.E016.
check_reverse_name_collisions reuses the collection but drops its errors, so next.E016 and next.E028 never appear twice.
Route introspection#
Beside the abstract generate_urls, RouterBackend carries three concrete introspection methods, page_roots, components_folder_name, and skip_dir_names.
They are a public contract, the only thing the system checks and the development watcher read from a backend, and Write a router backend is the canonical description of each method and of the safe defaults that make overriding them the opt-in.
The checks walk the reported trees themselves, refusing the names skip_dir_names returns plus the folder components_folder_name names, while the watcher derives its watch specs from page_roots and components_folder_name alone, as Autoreload describes.
RouterManager.backends is the public read of the loaded backend list, a tuple in routing order.
Reading the property loads no backends.
The list appears after the first iteration of the manager or after reload(), so a caller that needs the configured set asks after one of those.
Extension points#
Subclass
RouterBackendto feed the resolver from a different source, or subclassFileRouterBackendto add patterns or augment naming on the file-based backend.Register a custom backend in
RouterFactoryand reference it through the settings dotted path.Subscribe to
route_registeredto observe each new pattern. It fires once per discovered pattern withsender=FileRouterBackendand theurl_pathandfile_pathkeyword arguments. See Signals reference.
See also#
See also
File router for the topic guide. URL reversing for the reverse helpers. Autoreload for the reload mechanics tied to the watcher.