Repository navigation
[6.x] Reuse the nav tag's tree when a nav is output more than once - #15845
Merged
jasonvarga merged 10 commits intoNov 11, 2026
Merged
Conversation
A page often outputs the same nav more than once: a desktop menu and a mobile one, say. Each tag built the tree, augmented every page and worked out every URL again. The built tree is now kept for the rest of the request. Parameters that only change how much of it is shown (max_depth, limit, offset, select, include_parents) aren't part of what identifies it, so a tree built to a greater depth, or one that reached the bottom, serves a shallower tag too. What a page contributes wherever it is shown (its augmented values and its URLs) is kept per page object. is_current and is_parent are still worked out for each tag. The tree's own contents are part of the key, so a tree saved partway through a request is built again.
The pages still to be augmented kept their positions in the tree, but the augmentor numbers what it's given from zero, so once some pages were already known the results were matched to the wrong ones and the rest were never recorded.
DanielDarrenJones
marked this pull request as ready for review
November 11, 2026 17:01
An entry saved after a nav was output in the same request is now picked up by later nav tags, the same way Entry::save already clears its other Blink caches. The queue worker test now changes the entry without saving, since a save no longer leaves the stale tree behind. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The page data kept for a nav tag was keyed on the selected fields, with no select written as "*". That's the same key as select="*", which only asks for a field literally named "*", so whichever tag ran first decided what the other one output. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A negative max_depth builds an empty tree, which was then kept as complete and served to a later tag with no max_depth. The other way round, it was cut from a kept tree and showed the top level. Such a tag now builds its own tree and keeps nothing, as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A subclass can override the protected query methods to filter by something other than its parameters, such as the context it's rendered in. A kept tree skips those methods, so a later tag would show the first tag's pages. Subclasses now build their tree each time, as before, and can override reusesTrees() to opt in. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A page often outputs the same nav more than once: a desktop menu and a mobile one is the usual case. Each
{{ nav }}tag builds the tree, augments every page in it and works out every URL from scratch, even when an identical tree was built a moment ago for the tag above it.The tree a tag builds is now kept for the rest of the request and reused by later tags for the same nav.
What counts as the same tree
Parameters that change which pages are in the tree (
show_unpublished,include_home,from,site, conditions) identify it. Parameters that only change how much of it is shown don't:max_depth,limit,offset,select,include_parents,asandscope.max_depthserves any depth.Page::hasChildren()is new, and is how the tag knows whether it was cut short.is_currentandis_parentare still worked out for each tag, so the current URL isn't part of what's kept.What is kept
WeakMapheld in Blink, so it's keyed by the object itself and goes when the page does.Both last for the request, like anything else in Blink.
A tree saved partway through a request
The tree's own contents are part of the key, so a nav or collection tree that's saved after a tag has output it is built again.
it_sets_is_current_and_is_parent_for_a_navsaves a second tree halfway through and fails without this.An entry in the nav that's changed after the nav was output (its title, URL or status) is not picked up by a later tag in the same request.
Queue workers and Octane
Octane gets a fresh Blink for each request, so nothing is carried from one request to the next.
A queue worker has no requests, and Blink isn't cleared between its jobs. What the tag has kept is dropped when a worker moves on to its next job, so a job that outputs a nav doesn't see the one an earlier job built. This follows the same rule as the Stache's own reset between jobs: only in a
queue:orhorizon:process, so a job run synchronously during a request leaves that request's trees alone.Benchmark
Each page requested with and without this change, one PHP process per request with a warm Stache, run alternately for 3 rounds of 6 (Apple M1 Pro, PHP 8.5,
6.xat 5b60554). The site is a generated one: 358 entries, 65 Antlers templates, and a three-level main nav of 155 items output twice in the header, once withmax_depth="3"and once with no parameters, plus a footer nav. The bench is at https://github.com/1994limited/statamic-bench.The saving is roughly 15 to 25 ms a page, which is the second build of the main nav. A site that outputs each nav once gains nothing, and loses nothing measurable. Peak memory moved by less than 3 MB either way.
The HTML of every page is byte for byte the same before and after.