Notes

Routing is guessed one way and always reported

When a deploy leaves routing out, juudd calls a lone root index.html an app and everything else a static site, and the reply says which it chose and why.

If your assistant deploys a site to juudd without saying how to answer a URL that matches no file, we pick for it, and the reply tells you what we picked. A bundle whose only HTML is a single root index.html is treated as an app (spa). Anything else is treated as a static site (static). The reply ends in Routing: spa (detected) or Routing: static (detected), and (as given) when the caller chose.

This post is about why the guess is that narrow, and why saying it out loud is what makes a guess acceptable at all.

Two answers to the same URL

Ask an AI for a web app and it builds a Vite project: one index.html shell and hashed JavaScript. /dashboard is not a file in that build. It is a route the browser resolves after the shell loads. So a visitor who types that address, reloads the page they are on, or follows a shared link asks the server for a path that matches nothing.

We measured this against a real React build that uses a router:

routing/dashboardserves the app shell
static404no
spa200yes

Before routing was a setting, every site we published answered 404 for any path that was not a file. That is correct for a site whose pages are files, and wrong for the thing this product is for. The app worked perfectly when clicked through and broke the moment someone reloaded it.

The two mistakes are not the same size

We could have detected harder. We chose not to, because wrongly guessing in each direction costs something different.

An app wrongly called static breaks every deep link immediately and visibly. The first person to reload a page sees it, the cause is one word, and the fix is one redeploy.

A static site wrongly called spa answers 200 and the homepage for every typo, every dead link and every retired URL, forever. It tells no one. It also teaches search engines that a thousand pages exist. Nothing looks broken, so nobody looks.

Between a failure that reports itself and one that does not, we take the loud one. It is the same reason a missing site answers 404 rather than 502, and the same reason base64 that lost bytes is refused at the door instead of served short. So the guess fires only on the shape that is not in doubt, and static is what everything else gets.

The guess is stated every time

A detection nobody is told about is how the quiet failure happens. So the deploy_site reply always states the routing and whether it was given or detected. A detection that announces itself is a suggestion the caller can override in the same breath.

The reply says one more thing, and only on spa. Every path that matches no file answers 200 with index.html, and that includes a misspelled one and a missing script or image. A bad script path therefore fails in the browser as a syntax error on <, while the network tab reports success. The tool's description already says that spa serves index.html with 200, but a client keeps the description it read when it connected. The sentence in the reply is read at the moment the choice was made.

Why routing lives in the bundle

A bundle must be reconstructible from the stored artifact alone. That is why the subdomain is stored: a rebuild cannot recompute it. Routing is the same kind of fact. Rebuilding a site somewhere else has to know whether /dashboard is a page or a route, and nothing in the files reliably says so.

Left as an argument to the publish call, that knowledge would live only in whoever happened to make the call. Stored in the manifest, a rebuild reproduces it.

What follows from it

  • Old bundles read as static. Manifests written before the field existed

have no routing, and static is what those deploys were actually published as. An old bundle rebuilds into the behaviour it already had.

  • Routing does not change the version hash. The hash covers file contents.

Flip the setting and redeploy and you get a new version with the same content hash, which is honest: the same files, served differently.

  • A change takes a few seconds. Roughly five to ten seconds were measured,

and a probe fired the instant the upload returns can read the previous deploy's setting. That looks exactly like the feature not working.

  • spa makes a real 404 unreachable for that site. A customer who wants a 404

page inside an app has to handle it in the client, because the platform answers 200 for everything.

What this does not settle

Two things stay open. A customer who wants a real 404 page inside an app has no platform answer yet, only the client-side one above. And we have not measured how often a caller omits routing and gets the wrong guess: the argument for the narrow rule is about the size of the two mistakes, not their count.

The rule, once

Routing is static or spa, stored with the bundle. Omitted, it is spa only for a lone root index.html and static for everything else, and every deploy reply says which one it used and whether it was given or detected. The tools and their limits are listed in the docs.