Add subdirectory routing mode for public domain deployments

Apps can now be served at domain.com/appname/ instead of requiring
subdomain DNS records (appname.domain.com). Supports three subpath
modes per template: native (URL base env var), strip (handle_path),
and none (incompatible warning). Tested on Linux with deploy/removal
lifecycle verified.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-06 03:03:17 -08:00
parent f61e85d9a7
commit 77030931b7
13 changed files with 407 additions and 41 deletions

View File

@@ -25,6 +25,7 @@
<div class="form-hint-sm">
Your app will be available at: <span id="deploy-url-preview" style="color: var(--accent); font-weight: 500;">uptime.home</span>
</div>
<div id="subpath-compat-warning" style="display: none; padding: 8px 12px; border-radius: 6px; background: color-mix(in srgb, #ff9800 10%, var(--card-bg)); border: 1px solid color-mix(in srgb, #ff9800 30%, transparent); margin-top: 8px; font-size: 0.85rem;"></div>
</div>
<!-- DNS Configuration -->
@@ -407,6 +408,25 @@
const defaultSubdomain = appTemplate.subdomain || appTemplate.id.replace(/-/g, '');
subdomainInput.value = defaultSubdomain;
// Show subpath compatibility warning in subdirectory mode
const subpathWarning = document.getElementById('subpath-compat-warning');
if (subpathWarning) {
if (SITE.routingMode === 'subdirectory') {
const support = appTemplate.subpathSupport || 'strip';
if (support === 'none') {
subpathWarning.style.display = 'block';
subpathWarning.innerHTML = '<span style="color: #ff9800;">&#9888; <strong>' + appTemplate.name + '</strong> does not support subdirectory mode. It may not work correctly at a subpath.</span>';
} else if (support === 'strip') {
subpathWarning.style.display = 'block';
subpathWarning.innerHTML = '<span style="color: var(--muted);">&#9432; ' + appTemplate.name + ' has unverified subdirectory support. It may require additional configuration.</span>';
} else {
subpathWarning.style.display = 'none';
}
} else {
subpathWarning.style.display = 'none';
}
}
// Pre-select DNS/SSL from site config (set during setup wizard)
const cfgDnsType = SITE.defaults.dnsType || (SITE.configurationType === 'public' ? 'public' : 'private');
const cfgSslType = SITE.defaults.sslType || (SITE.configurationType === 'public' ? 'letsencrypt' : 'internal');
@@ -622,7 +642,10 @@
const sslType = document.querySelector('input[name="ssl-type"]:checked').value;
let url = '';
if (dnsType === 'private') {
if (SITE.routingMode === 'subdirectory' && SITE.domain) {
// Subdirectory mode: domain.com/app
url = `https://${SITE.domain}/${subdomain}`;
} else if (dnsType === 'private') {
const protocol = sslType === 'none' ? 'http' : 'https';
url = `${protocol}://${buildDomain(subdomain)}`;
} else if (dnsType === 'public') {