Can you link to what you are looking at? A proposed URL-state axis for AI app builders (September 2026)
AI app builders are reliable on the path you walk while building. A proposed axis for what happens on the second arrival: reload, Back, or a pasted link.
Updated on September 18, 2026
On this page
An AI app builder is very good at the path you walk while you are building. You ask for an orders dashboard, you click into it, you filter to the unpaid ones, you open a record, you make a change. Every screen appears. Nothing is broken.
Then somebody presses the browser Back button, and the app leaves. Or they reload the filtered list and get the unfiltered one. Or they paste the address of the record they are looking at into a message and the person who opens it lands on an empty dashboard, because the address never described the record in the first place.
None of that is a rendering bug. The views are fine. What is missing is that the application never wrote down where the user is, so there is no way to arrive at the same place twice. This post proposes a BuilderProof benchmark axis to measure that directly, and sets out how it would be scored reproducibly. As with every axis proposal on this site, it is a pre-registration rather than a report: it publishes a rubric and a protocol before any builder is measured against them, and it assigns no score to any vendor.
What the axis measures
URL-state addressability scores whether the application a builder generates records its own navigational state somewhere durable enough that a view can be reached a second time, by a route other than walking forward through the interface.
For every view a user can reach, the axis asks three things. Is the identity of this view written into the address. If that address is loaded cold, does the same view come back. And if the user traverses the session history to it, does the application behave as though it was expecting that.
It is a property of the generated application, not of the builder's chat interface and not of its preview pane.
The trap: the forward-path illusion
Every axis on this site names the illusion that hides its defect. This one is the forward-path illusion.
The defect requires a second arrival at a view by a route other than the one that created it: a reload, a Back press, a pasted address, a bookmark opened the following morning. Building an application produces none of those. You add a screen, you click to it, you confirm it renders, you move on. The act of construction is itself a forward walk, and a forward walk cannot generate the observation that would expose the problem.
This is the sharpest member of a family several of our axes share, where every observation available at test time is produced by the same actor. Here the actor is the person walking forward, and the point is not merely that they fail to notice. It is that the workflow which would notice is the one workflow nobody performs while building.
Two consequences follow. The first is that the defect survives review, because a reviewer reads the diff and the diff is correct. The second is that it surfaces at the worst possible moment, when a real user shares a link, because sharing a link is the first time anybody asks the application a question it was never asked during construction.
What the platform actually specifies
The web platform is unusually explicit here, and reading the specifications rather than the framework guides changes what the rubric should reward.
The HTML Standard defines a session history entry as carrying, among other fields, a URL, a serialized state object, and a scroll restoration mode. It also tells you plainly what the state object is for. Verbatim: "it could be used to keep a pointer into a cache of data that would be fetched from the server based on the information in the URL, so that when going back and forward, the information doesn't have to be fetched again." The specification's own worked example assumes the URL carries enough to reconstruct the view. An application whose address bar does not identify the view is not using the mechanism the platform was designed around.
The same section defines the scroll restoration mode, and the default is worth quoting exactly, because it is a default nobody chooses. Verbatim: "scroll restoration mode, a scroll restoration mode, initially 'auto'." The two values are then defined as follows: under auto, "The user agent is responsible for restoring the scroll position upon navigation", and under manual, "The page is responsible for restoring the scroll position and the user agent does not attempt to do so automatically". A generated application that never touches this has not selected browser restoration. It has simply inherited it, and in a client-rendered list where the rows arrive after the restoration attempt, inheriting it is frequently the wrong outcome.
MDN records two facts about the popstate event that between them explain why this defect is so easy to ship. The first is that the obvious test does not fire the event. Verbatim: "Note that just calling history.pushState() or history.replaceState() won't trigger a popstate event. The popstate event will be triggered by doing a browser action such as a click on the back or forward button." A developer who adds history entries and then clicks around the application will never see the handler run, because the handler only runs on the traversal nobody performs while building.
The second is stranger and is worth knowing before you write a probe. Verbatim: "to combat unwanted pop-ups, browsers may not fire the popstate event at all unless the page has been interacted with." A scripted traversal that never clicks anything can therefore observe a popstate handler failing to run for a reason that has nothing to do with the application.
MDN also records the event that tells you a page came back from memory rather than from the network. Verbatim: "The pageshow event fires right after the load event when the page is initially loading and any time the page is restored from bfcache. The pageshow event has a persisted property, which is true if the page was restored from bfcache and false otherwise." That property is the only cheap signal distinguishing a fresh load from a restore, and an application that never reads it cannot tell the difference between a user arriving and a user returning.
Eligibility for the back and forward cache, and why it is not free
The back and forward cache keeps a whole document in memory so that a traversal restores it instantly instead of re-fetching and re-rendering it. Chrome's own documentation is precise about what disqualifies a page, and two of the disqualifiers are things an application does deliberately for other good reasons.
The first is a cache directive. Verbatim: "Although bfcache is not an HTTP cache, historically, when Cache-Control: no-store is set on the page resource itself (as opposed to any subresource), browsers have chosen not to store the page in bfcache so any pages using Cache-Control: no-store may not be eligible for bfcache." The same page states the remedy in its own sentence: "For pages that need to always serve up-to-date content, and that content does not contain sensitive information, use Cache-Control: no-cache or Cache-Control: max-age=0. These directives instruct the browser to revalidate the content before serving it, and they don't affect a page's bfcache eligibility."
The second is an open connection. Verbatim: "some browsers won't attempt to put a page in bfcache in the following scenarios: Pages with an open IndexedDB connection. Pages with in-progress fetch() or XMLHttpRequest. Pages with an open WebSocket or WebRTC connection." The documentation notes that Chrome and Safari do not block on open WebSockets while other browsers do, and it prescribes the lifecycle fix directly: close the connections during pagehide, reopen them during pageshow.
There is also a consequence of restoration that a rubric has to account for rather than reward blindly. Verbatim: "when a page is restored from bfcache, it is restored from memory, not from the HTTP cache. As a result, directives like Cache-Control: no-cache or Cache-Control: max-age=0 are not taken into account, and no revalidation occurs before the content is displayed to the user." An instant restore is a good user experience and it is also a screen that may be showing data from before the traversal. Handling that is part of the axis. Being eligible for restoration is not, on its own, the same as being correct after one.
Where the framework pushes back
It would be easy to treat this axis as a matter of discipline, as though putting state in the address were free and generators simply forget. The documentation of the framework these builders most often emit says otherwise, and it says so about its own product.
Next.js documents that reading the query string from a client component has a rendering cost. Verbatim: "If a route is prerendered, calling useSearchParams will cause the Client Component tree up to the closest Suspense boundary to be client-side rendered." So moving a filter out of component state and into the address is not a neutral refactor. It changes how much of the route can be prerendered.
More pointedly, the same page records a structural limit. Verbatim: "Unlike Pages, Layouts (Server Components) do not receive the searchParams prop." A layout cannot see the query string at all. An application whose navigation chrome lives in a layout therefore cannot read the very state this axis asks it to store there, without pushing that concern down into a client component and paying the rendering cost above.
This is the shape worth recording. The framework's own documentation describes, accurately and about itself, a set of incentives that point away from the behaviour the axis rewards. That does not make the axis wrong. It makes it a measurement of a decision rather than of an oversight, and it means the rubric should reward the decision having been made rather than assume it was cost-free.
The rubric
Seven signals, weights summing to 100. The failing description is what a zero looks like, so the rubric can be applied by inspection and by probe rather than from a vendor's description of itself.
Scroll to see more
| Signal | Weight | What a failing case looks like |
|---|---|---|
| The view's identity is in the address | 22 | Filters, the selected tab, the opened record and the position in a list live only in component state, so several distinct screens share one address and that address names none of them |
| A cold load of the address reconstructs the view | 20 | The address carries the parameters but a fresh load ignores them and renders the default view, or redirects to a dashboard root, so the address is decorative |
| Traversal produces the previous view | 16 | An in-app navigation that changed the screen pushed no history entry, so Back leaves the application entirely; or the address changes while the rendered screen does not |
| Restoration from memory is handled | 14 | The document is ineligible for the back and forward cache because of an unnecessary no-store, an unclosed connection or a legacy unload listener; or it is restored and silently shows data from before the traversal because nothing reads the persisted flag |
| The address carries intent, not internals | 12 | The only record of position is a raw primary key, a session identifier or an opaque continuation cursor, so the address is neither legible to the user nor safe to share |
| Scroll and focus after a traversal are a decision | 9 | The scroll restoration mode is inherited rather than chosen, and a user returning to a long list is dropped at the top of it, or the application and the browser both attempt restoration and fight |
| A traversal does not replay a mutation | 7 | Back or reload after a submission re-issues it, or the address reached after a completed action still addresses that action |
Four postures
These are structural descriptions. No builder is placed in any of them here.
Level 0, Ephemeral. One address for the entire application. Every screen beyond the entry point exists only as component state. A reload returns the user to the beginning, Back leaves the site, and no view can be shared or bookmarked. This is the natural output of generating an interface as a single component tree and never being asked about the address bar.
Level 1, Addressed by route. Top-level pages have their own addresses. Everything within a page does not: the filter, the selected tab, the opened record, the position in a list. This is the most common posture and the most deceptive one, because the application looks addressable. Sharing the address of a page reliably produces the page, and reliably does not produce the view.
Level 2, Addressed by state. The parameters that define the view are in the address, and a cold load of that address reconstructs it. At this level an application is genuinely shareable and genuinely bookmarkable. It may still traverse badly.
Level 3, Traversable. As Level 2, plus the session history is correct. Each meaningful view change produces exactly one history entry, restoration from memory is handled rather than merely permitted, scroll behaviour is a decision, and a traversal past a mutation does not replay it.
The gap that matters most is between Level 1 and Level 2, and note that it is not a matter of degree. A Level 1 application does not partially share a view. It shares a different view, confidently, with no error and no indication that anything was lost.
The measurement protocol
Ten steps. The point of a BuilderProof protocol is that somebody else can run it and get the same picture, so the probes are stated in terms of observable outcomes rather than implementation.
- Fixed prompt. Each builder receives an identical, version-pinned brief from the fixed-prompt suite asking for a list view with at least one filter, a detail view, and a create or edit form. The brief says nothing about addresses, history or sharing. The omission is deliberate: this measures defaults, not instruction-following.
- Enumerate the views. From the running application, list every distinct thing a user could reasonably want to return to. Record the address shown for each. If two views share an address, that is the finding, and it is recorded before any further probing.
- The looks-correct control. Before probing, confirm that walking forward through every enumerated view works. If it does not, the application has a different defect and this axis is not what is being measured. A run in which the forward path fails is discarded rather than scored, because the whole claim of the axis is that the forward path passes.
- Cold load. For each address recorded in step 2, open it in a fresh browser context with no prior session in that tab. Record whether the view that appears is the view the address was taken from, a default view, an error, or a redirect.
- Share. Take the same addresses to a second browser profile, authenticating if required. This separates state held in the address from state held in storage local to the first profile, which a reload in the same tab cannot distinguish.
- Reload in place. Reload each view without leaving the tab. A view that survives a reload but not a cold load is keeping its state in session storage rather than in the address, which is a different posture and should be recorded as such rather than scored as a pass.
- Traverse. From the deepest enumerated view, press Back once per navigation performed on the way in, recording the rendered view and the address at each step. Count the entries. An application that pushed no entry exits the site; one that pushed several per navigation requires several presses to move one step.
- Restore. Navigate away to a different origin and return with Back. Record whether the document was restored from memory, using the persisted flag, and if it was, whether anything on screen is stale. Separately, record the eligibility inputs: the cache directive on the document, any open connections, any legacy unload listener.
- Replay. Perform a mutation, then press Back, then reload. Record whether the mutation is re-issued and whether the resulting address still addresses a completed action. Run this against a record whose duplication is observable.
- Score and evidence. Score each signal against the rubric with the recorded address, the rendered view and the history-entry count attached, so a score is auditable rather than asserted.
Three tensions with axes already published here
An axis that conflicts with nothing is usually measuring nothing. This one conflicts with three of our own, and in each case the resolution is scope or ordering rather than a choice between them.
Against HTTP caching. Our HTTP caching and revalidation axis carries private-response scoping at weight 24, and a response carrying one user's data with no private or no-store directive is a failing case there. Applied reflexively to every authenticated document, that reward removes the document from the back and forward cache, which is a failing case here. The resolution is the one Chrome's documentation states itself: no-store belongs on documents that genuinely contain sensitive material and on the API responses carrying that material, while a page that merely needs to be fresh should use no-cache or max-age=0, which does not affect eligibility. The two axes disagree only when a single directive is applied without distinguishing sensitive from merely personal.
Against realtime subscriptions. Our realtime subscription correctness axis rewards a live channel that stays connected and delivers. An open socket or an open database connection is precisely what can make a document ineligible for restoration. The resolution is ordering rather than scope: close during pagehide, reopen during pageshow. An application that does this scores well on both, and an application that has never considered the page lifecycle scores well on neither once it is actually traversed.
Against pagination. Our pagination and large-collection read axis rewards an opaque continuation token at weight 12, on the grounds that a parseable cursor invites clients to construct one. This axis rewards an address a human can read and share. These are not actually in conflict once the two are separated: the address should carry the user's intent, which is the filter, the sort and the page they believe they are on, while the continuation token stays in the request. An application that puts a raw cursor in the address bar fails both at once, which is the cleanest evidence that they are measuring different things.
What this axis is not
It is not state-handling completeness. Our state-handling completeness axis asks whether a view renders something sensible while data is loading, when there is none, and when a request fails. This axis asks whether the view can be reached at all by a second route. They move in opposite directions and both directions are real. An application can carry a skeleton, an empty state and an error boundary on every data-bound view while keeping its entire navigation in component state, scoring at the top of that axis and the bottom of this one. An application can put every filter and every opened record in the address and still render a blank region for two seconds on arrival, scoring the reverse.
It is not crawler-facing metadata. Our generated-app SEO and meta output axis is about what a crawler receives on a first arrival at a page. This axis is about what a returning human receives on a second arrival at a view. A canonical address that is correct for indexing says nothing about whether a filtered list can be shared, and a perfectly shareable internal view may be one no crawler should ever see.
It is not cold start, and the two compose. Our cold start and first-request latency axis measures what the first visitor waits for after an idle period. This axis measures whether a second arrival at the same view is possible at all. They compose in a way worth stating: a restoration from memory is instant and involves no request, so it bypasses the cold path entirely, and an application that is ineligible for restoration pays the cold-start cost again on every Back press. Neither axis can see that on its own.
It is not a request deadline. Our outbound-call failure axis quotes MDN on a timeout that pauses while a document is in the back and forward cache. That is a property of the deadline. This axis is concerned with whether the document is restorable at all, which is a different question about the same mechanism.
Limitations and open questions
- The view enumeration in step 2 is the weakest part of the protocol and it is a judgement call. What counts as a view somebody would want to return to is not defined by anything in the application, and two people running this will produce different lists. Until that is pinned to the fixed prompt, the enumeration should be published alongside the score rather than treated as obvious.
- Restoration eligibility is browser-dependent, and the documentation says so. Chrome and Safari do not block on open sockets where other engines do. A single-engine probe will therefore report an eligibility that is not general, and the honest options are to name the engine with the score or to probe more than one. We currently favour naming the engine.
- Much of this is inherited from the framework rather than authored by the generator. Where routing, history and restoration behaviour come from the framework a builder emits, two builders on the same framework will score nearly identically, which says very little about either. This is the same problem our pagination proposal records about inherited paging primitives, and we do not have a better answer here than reporting inherited and authored behaviour separately.
- The replay probe in step 9 overlaps concurrent-write safety. A mutation re-issued by a reload is a duplicate write, and duplicate writes are already scored elsewhere. The boundary we intend is that the other axis asks whether a duplicate write is prevented, while this one asks whether an ordinary traversal is capable of producing one. Whether that boundary survives contact with real scoring is genuinely unresolved.
- We have read platform specifications and one framework's documentation for this proposal, and no builder output. That is a deliberate scope choice for an axis proposal and it is also the largest gap on this page. The postures are structural. None of them has been tested against a generated application, and nothing here should be read as a score.
Counter-rubrics, corrections and reproduction attempts are welcome, and the most useful thing anyone can send us is a history-entry count from step 7 against a project of their own, with the number of navigations performed on the way in. A rubric is easiest to correct before it has ever produced a score.
References
- WHATWG HTML Standard, "Browsing the web" and "Navigation and session history APIs": the composition of a session history entry, the initial scroll restoration mode of auto, the definitions of auto and manual, and the worked example of the state object as a pointer into a cache keyed by the information in the URL. https://html.spec.whatwg.org/multipage/browsing-the-web.html and https://html.spec.whatwg.org/multipage/nav-history-apis.html
- MDN, "Window: popstate event": that pushState and replaceState do not themselves trigger popstate, and that browsers may not fire popstate at all unless the page has been interacted with. https://developer.mozilla.org/en-US/docs/Web/API/Window/popstate_event
- MDN, "Window: pageshow event": that pageshow fires on restoration and carries the persisted property. https://developer.mozilla.org/en-US/docs/Web/API/Window/pageshow_event
- MDN, "History: scrollRestoration property": the auto and manual values. https://developer.mozilla.org/en-US/docs/Web/API/History/scrollRestoration
- Chrome for Developers, "Back and forward cache": the Cache-Control no-store eligibility behaviour and the recommended alternatives, the list of connection states that block caching, the pagehide and pageshow lifecycle remedy, and the note that a restore from memory does not revalidate. https://web.dev/articles/bfcache
- Next.js documentation, "useSearchParams": that reading search params causes the client tree up to the nearest Suspense boundary to be client-side rendered, and that Layouts do not receive the searchParams prop. https://nextjs.org/docs/app/api-reference/functions/use-search-params
All quotations were read from the pages above on 18 September 2026.
Written by
BuilderProof editorial teamCite this benchmark
BuilderProof editorial team. "Can you link to what you are looking at? A proposed URL-state axis for AI app builders (September 2026)". BuilderProof, September 2026. https://www.builderproof.org/benchmarks/can-you-link-to-what-you-are-looking-at-url-state-axis-september-2026.
@misc{builderproof-can-you-link-to-what-you-are-looking-at-url-state-axis-september-2026,
title = {{Can you link to what you are looking at? A proposed URL-state axis for AI app builders (September 2026)}},
author = {{BuilderProof editorial team}},
year = {2026},
month = {sep},
howpublished = {\url{https://www.builderproof.org/benchmarks/can-you-link-to-what-you-are-looking-at-url-state-axis-september-2026}},
note = {BuilderProof, builderproof.org}
}Frequently asked questions
What is the URL-state addressability axis?
It is a proposed BuilderProof benchmark axis that scores whether the application an AI app builder generates records its own navigational state, such as filters, the selected tab, the opened record and the position in a list, somewhere durable enough that a view can be reached a second time by reloading, traversing session history, or opening a pasted address.
Why would a working app fail this axis?
Because building an application is itself a forward walk through it. The defect only appears on a second arrival at a view by another route, such as a reload, a Back press or a shared link, and none of those happens while the application is being constructed. We call that the forward-path illusion.
Does putting state in the URL conflict with other benchmark axes?
With three of ours, and each resolves by scope or ordering rather than by choosing. Cache-Control no-store scores well on our HTTP caching axis and can make a document ineligible for the back and forward cache. An open live channel scores well on our realtime axis and can block restoration, which the page lifecycle events fix. And our pagination axis rewards an opaque cursor, which belongs in the request rather than in the address bar.
Has any AI app builder been scored on this axis?
No. This is an axis proposal, which on BuilderProof means a pre-registered rubric and protocol published before measurement. No builder is placed in any posture on this page, no score is assigned, and the postures are structural descriptions only.
Related benchmarks
Realtime Subscription Correctness: A Proposed Axis for Whether a Generated Live View Ever Notices It Stopped Being Live (September 2026)
A candidate BuilderProof benchmark axis scoring whether a generated application's live views converge back to the true server state after the connection carrying their updates is interrupted. From the database notification layer upward, every delivery primitive is documented as reaching whoever is connected at that instant, with no backlog for anyone who was not, and every recovery mechanism is opt-in.
Pagination and Large-Collection Read Correctness: A Proposed Axis for Whether a Generated List Returns Every Row Exactly Once (September 2026)
A candidate BuilderProof benchmark axis that scores whether the read path an AI app builder emits returns each record of a collection exactly once while the collection is being traversed, and whether read cost stays bounded as the table grows. Seven weighted signals, four structural postures, a traversal protocol, and the point on which three independent pagination specifications agree and generated clients routinely violate.
State-handling completeness: a proposed benchmark axis for AI app builders (August 2026)
State-handling completeness is a proposed BuilderProof benchmark axis (August 2026) that scores how well an AI app builder generates the non-ideal runtime states of the apps it produces: loading, empty, and error states. It is a 20-point axis across five sub-criteria, measured reproducibly by giving all five commercial builders (v0, Lovable, Replit, Base44, Bolt.new) an identical fixed prompt and then inspecting the generated app under a throttled network, an empty account, and a forced request failure.