Deep linking, query params, and entrypoints

hi folks. i’m considering using hotwire for a new project so i can hopefully escape the (imo) complexity trap that SPAs have become. i’m serving from a clojure-backed webserver, so this is strictly a clientside-concerned implementation.

i’ve read pretty much all the material i can find on the hotwire packages (turbo, stimulus, strada), but there are a few very important outstanding questions to which i cannot find the answer. hopefully someone here can enlighten me.

  1. is there a standard expectation/implementation for deep linking? if your webserver is supposed to be serving well-cached html fragments via frames, or dynamically updating html fragments via streams and websockets/SSE, as opposed to complete html documents, does this presume you have a single top-level entrypoint for the app? this entrypoint would presumably serve the turbo.js package (once), and some top level entrypoint html for your app. after this entrypoint, turbo intercepts all routing and begins performing the html-swapping magic. but how does this interplay with a pretty standard “deep link” into an app such as /organization/{id}/project/{id}/tasks? in a “turbo” request, my server would parse the organization-id and project-id provided by the turbo-a or turbo-form, then use it to serve up the related tasks html. it wouldn’t serve up the entire document, because the organization and project html is already on the page via a client’s previous navigation through the app. but if a client requests that /organization/123/project/456/tasks GET as their initial visit to my domain … what then? do I have to have multiple versions of an endpoint based on some turbo header, where the absence of a turbo request results in: “serve the turbo.js, and the entire top-level document headers, body, etc, and also render the html elements related to the organization, and project, and also tasks” (as if this was a standard, “old school” html request).

  2. related to #1, what about query parameters? is it the expectation that the browser’s url stays “in sync” with any forms/frames that make mutating requests to the server? for example, if i have a frame rendering a list of entities with clickable headers to enable sorting: clicking on one of the sorting headers would <a> link to some …entities?sort[param]=whatever, which the server receives, queries, sorts, and returns the entities’ sorted html. do i have to wire up hooks in the javascript environment to mirror the URL state with the html state? what if someone wants to sort the entities, then copy the URL (to share with a coworker), but the URL doesn’t update?

maybe I missed an important part of a tutorial / walkthrough somewhere, but I feel like I missed the boat on routing as it relates to hotwire.

oh my, i wonder if Turbo Reference solves about half my confusions. a frame, by default, doesn’t advance/modify the browser’s URL. but you can promote this behavior via action="advance". the server’s various endpoints still needs to properly handle query params, but that’s pretty standard. i’m thinking of the instances where two frames might both modify the URL (input[type]=foo from one frame, and sort[direction]=bar from another), but both sets of a frame’s query parameters are still relevant (input[type]=foo&sort[direction]=bar).

i wonder if the better architectural strategy is to still build your entire app to return the entire html document, every time. rather than sectioning off routes into fragment-html endpoints, manage entire pages comprehensively (shared query strings, stateful html, etc). you can then leverage turbo’s auto-replacement of just a frame’s content as needed- but it’s a progressive enhancement at that point. this defeats the point of everything, basically. you want sectioned off json-like endpoints for resources and collections and their operations. the difference is they return html fragments instead of json. failing to introduce resource endpoints reintroduces the caching nightmare (you cant/shouldnt cache an entire page that has some frequently dynamic content)[Turbo Handbook], and you end up sending way more bytes over the wire.

this makes my question about entrypoints all the more important, i think. if you are exposing resource-based html-fragment endpoints, but have to ensure a copy of turbo.js already exists on your client, is there a reasonable way to organize your endpoints so that first-visit “deep links” 1) return an entire document with all the necessary js/css assets, 2) recognize that the endpoint cannot serve only a fragment at this point, but must serve the entire document contextualized based off the given route.


I’m unable to delete this post :frowning: . I have made a more concise question here: How to handle entrypoints into an app?