Use tilde paths in JavaScript

Pitfall By design
Hardcoded leading-slash paths in JavaScript break the moment the app is published under a subfolder. On publish, Docly rewrites href and src attributes in static HTML — but not form action, and nothing JS emits at runtime. Use ~/ in all JS path strings and in any non-href/non-src HTML attribute that holds a path; it resolves to the current webapp's base path at runtime and works equally at the root. Resolution happens on the inbound request, not in the served output: the tilde reaches the browser intact, the request arrives as /app/~/API/hello, and the server resolves the segment then. Seeing ~/ in view-source or the network panel means it is working — do not "fix" it away.

What you'll see

A frontend or hash-rendered page works perfectly when developed at the site root, then breaks the moment the same app is published under a subfolder — typically a working copy at /test/, /staging/, or a per-customer branded path on the same domain. Symptoms include fetch() calls hitting 404, window.location.href assignments jumping out of the working copy and back to production, dynamically-built <img> sources failing to load, and login redirects landing on the wrong site.

The shared cause is hardcoded leading-slash paths in JavaScript — strings like '/API/articles', '/uploads/file.png', '/dashboard' — that worked at the root only by coincidence.

What's actually happening

Docly resolves ~/ at runtime to the base path of the current webapp. When the app is mounted at the domain root, ~/API/hello resolves to /API/hello. When the same code is published as a working copy under /test/, the identical string resolves to /test/API/hello. The application does not need to know where it is mounted — every ~/ reference rebases automatically.

Publish-time rewriting covers all href and src attributes in static HTML — <link href>, <script src>, <a href>, <img src>, and so on. Inside those, a leading / is rebased to the publish location automatically and you don't need ~/. What is not rewritten: <form action>, and any path string your JavaScript builds at runtime — fetch() URLs, window.location assignments, dynamic .src/.href assignments, template strings, AJAX endpoints. Those are passed through literally; Docly never inspects them.

Which raises the obvious question: if Docly never inspects the string, how does ~/ in a fetch() call ever resolve? It does not resolve on the way out. It resolves on the way in. The tilde is written to the response untouched, so the browser receives fetch('~/API/hello') verbatim, resolves it against the current page like any other relative URL, and requests /app/~/API/hello. The server strips the ~/ segment and everything before it, rebases onto the webapp root, and serves the target — then redirects a page request to its canonical URL. The segment is honoured at any depth, so it does not matter how deep the page emitting it sits.

Two consequences worth stating, because both look like faults and are not. The tilde survives into view-source — in the served HTML, in <form action>, and inside script blocks. And the network panel shows a URL containing ~/. Both are the mechanism working. The failure mode to watch for is the opposite one: someone verifies by diffing source against output, sees the tilde unchanged, concludes the feature is broken, and replaces it with a hand-built base path.

A hardcoded leading-slash path in JS code is therefore portable only as long as the app stays at the domain root. Mounted anywhere else, every such path points at the wrong place: production endpoints when the working copy expected its own, or 404s if the production site has nothing at that path. The first user to spin up a working copy typically discovers this when "everything is broken" at once, with no recent change to blame.

What to do

Use ~/ in every path string your JavaScript code emits. For static HTML, leading / is fine in any href or src attribute — Docly rewrites them on publish. The exceptions are <form action> and anything JS sets at runtime; use ~/ there. When in doubt, ~/ is always safe — it resolves correctly at the root too — so default to it.

Do — ~/ for everything dynamic:

fetch('~/API/articles')
fetch('~/API/login', { method: 'POST', body: JSON.stringify(creds) })
window.location.href = '~/dashboard'
img.src = '~/uploads/' + filename
return { redirect: '~/welcome' }   // returned from a #/API/ endpoint

Don't — leading slash in JavaScript:

fetch('/API/articles')              // hits production root from a /test/ working copy
window.location.href = '/dashboard' // jumps out of the working copy
img.src = '/uploads/' + filename    // 404 — file lives at /test/uploads/...

Static HTML — leading slash is fine in href and src (Docly rewrites these on publish):

<link rel="stylesheet" href="/ui/style.css">
<script src="/ui/app.js"></script>
<a href="/dashboard">Dashboard</a>
<img src="/logo.png">

Static HTML — use ~/ for action (not rewritten):

<form action="~/API/submit">...</form>

Verifying it — request the URL, do not diff the output. Comparing your source against the served response tells you nothing here, because the tilde is meant to survive that step. The only check that means anything is an actual request:

# from a page at /app/some/page — this is what the browser sends
curl -s -o /dev/null -w "%{http_code}\n" "https://<site>/app/some/page/~/API/hello"

A 200 (or whatever the endpoint returns for an unauthenticated call — 401 is a fine answer) means the segment resolved. A 404 means it did not reach the endpoint. If you want to see the rebasing on a site you already have open, request any page through a tilde: /Guidelines/KB/~/Introduction lands on /Introduction.

Working copies under subfolders are the standard way to stage changes in Docly — branded snapshots, per-customer test sites, demo deployments — without disturbing production. Code that uses ~/ consistently is portable across them with zero edits; code that hardcodes / in the wrong place is portable only by accident, and the breakage is silent until the first non-root deploy.