Fix: resolve data paths against app base for subdirectory deploys
All checks were successful
CI / test-and-build (push) Successful in 48s

The static app is served at /maps/ but the data manifest and graph/poi/layer
fetches used absolute /data/... paths, which resolved to the server root and
failed. Add src/lib/base.ts exporting upath() that prefixes a path with the
runtime-detected base (mirroring SvelteKit's 'base: new URL(".", location)'),
and apply it to the manifest, layer, graph and POI fetches. Works at both
root and any sub-path.
This commit is contained in:
hermes-explorigin 2026-08-16 14:39:58 +00:00
parent 8941d837b5
commit fb6ad3eb0c
3 changed files with 32 additions and 3 deletions

27
src/lib/base.ts Normal file
View File

@ -0,0 +1,27 @@
// Base-path-aware data URL resolution for the Navigator static app.
//
// The app is built with the static adapter and deployed to a subdirectory
// (e.g. /maps/). Its built index.html auto-detects the base from location
// (base: new URL(".", location)), so all internal asset/data fetches must be
// prefixed with that same base. Data manifest paths are stored as absolute
// (/data/...) by the build scripts, so we resolve them against the running
// location rather than the server root.
//
// E.g. served at https://site/maps/ -> base '/maps'; the manifest path
// '/data/_index.json' becomes '/maps/data/_index.json'.
export function appBase(): string {
if (typeof window === 'undefined') return '';
// Mirror the SvelteKit runtime detection: base = directory of the current
// page, with the trailing slash removed (so '/' -> '', '/maps/' -> '/maps').
const dir = new URL('.', window.location.href).pathname;
return dir.replace(/\/+$/, '');
}
// Resolve a path that may be absolute ('/data/x.json') relative to the app's
// base. Leaves already-relative and non-leading-slash/dotted paths untouched.
export function upath(p: string): string {
if (!p) return p;
if (p.startsWith('/')) return appBase() + p;
return p;
}

View File

@ -9,6 +9,7 @@
import { prefs } from '$lib/prefs'; import { prefs } from '$lib/prefs';
import type { RouteGraph, AvoidRule, RouteResult } from '$lib/routing'; import type { RouteGraph, AvoidRule, RouteResult } from '$lib/routing';
import { savedPoints, addSavedPoint, removeSavedPoint, exportSavedPoints, importSavedPoints, type SavedPoint } from '$lib/savedPoints'; import { savedPoints, addSavedPoint, removeSavedPoint, exportSavedPoints, importSavedPoints, type SavedPoint } from '$lib/savedPoints';
import { upath } from '$lib/base';
type FeatureCollection = { type FeatureCollection = {
type: string; type: string;
@ -184,7 +185,7 @@
if (!layer || !layer.graphPath || !layer.poiPath) { routeGraph = null; hasRouteGraph = false; return; } if (!layer || !layer.graphPath || !layer.poiPath) { routeGraph = null; hasRouteGraph = false; return; }
routeLoading = true; routeLoading = true;
try { try {
const [g, p] = await Promise.all([fetch(layer.graphPath), fetch(layer.poiPath)]); const [g, p] = await Promise.all([fetch(upath(layer.graphPath)), fetch(upath(layer.poiPath))]);
if (!g.ok) throw new Error(`graph HTTP ${g.status}`); if (!g.ok) throw new Error(`graph HTTP ${g.status}`);
if (!p.ok) throw new Error(`poi HTTP ${p.status}`); if (!p.ok) throw new Error(`poi HTTP ${p.status}`);
routeGraph = await g.json(); routeGraph = await g.json();
@ -420,7 +421,7 @@
try { try {
for (const layer of layers) { for (const layer of layers) {
try { try {
const res = await fetch(layer.path); const res = await fetch(upath(layer.path));
if (!res.ok) throw new Error(`fetch ${layer.path}: HTTP ${res.status}`); if (!res.ok) throw new Error(`fetch ${layer.path}: HTTP ${res.status}`);
const fc: FeatureCollection = await res.json(); const fc: FeatureCollection = await res.json();

View File

@ -1,6 +1,7 @@
<script lang="ts"> <script lang="ts">
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import MapView, { type MapLayer } from '$lib/components/MapView.svelte'; import MapView, { type MapLayer } from '$lib/components/MapView.svelte';
import { upath } from '$lib/base';
type AreaManifest = MapLayer; type AreaManifest = MapLayer;
@ -10,7 +11,7 @@
onMount(async () => { onMount(async () => {
try { try {
const res = await fetch('/data/_index.json'); const res = await fetch(upath('/data/_index.json'));
if (!res.ok) throw new Error(`manifest HTTP ${res.status}`); if (!res.ok) throw new Error(`manifest HTTP ${res.status}`);
areas = (await res.json()) as MapLayer[]; areas = (await res.json()) as MapLayer[];
} catch (e) { } catch (e) {