Skip to content

The UI ​

A prebuilt React SPA served by an Express router at whatever path you mount it. Your app's framework is irrelevant — a Vue host mounts this and never knows.

js
app.use('/shorty', requireAuth, pkg.ui({ mountPath: '/shorty' }));

One build, any mount path ​

Assets are referenced relatively and the server injects <base href="<mountPath>/"> plus a config blob per request. Remounting needs no rebuild.

The catch: mountPath must match where the router actually is. Behind a proxy that strips a prefix, it is the path the browser sees. Get it wrong and the HTML loads while every asset 404s — a failure that looks like a broken build and is a wrong string.

Two UIs, two guards ​

ui() and adminUi() are separate mounts, not one page with a client-side flag. A non-admin who guesses the admin URL is stopped by your middleware before any HTML is served — there is nothing in devtools to flip.

js
app.use('/admin/shorty', requireAuth, requireAdmin,
        pkg.adminUi({ mountPath: '/admin/shorty' }));

Options ​

OptionDefault
mountPath/shortyMust match reality. See above.
apiBase/api/shortyWhere the browser calls the JSON API
adminApiBase/api/shorty/adminSame, admin
titleShortyPage title
loginUrlnullWhere to send an expired session. Null surfaces errors inline.
returnParamnextQuery param carrying the return URL
spaDirthe shipped bundleOverride the built SPA directory

503 on a fresh checkout ​

If the bundle is missing, the router answers 503 with a line telling you to run npm run build. That is the normal state of a fresh git clone, not a bug — dist/ is not committed.

Theming ​

The SPA scopes its CSS to its own root element and reads prefers-color-scheme. It does not inherit host styles and does not leak into them.

Released under the MIT License.