static.urlPath subpath mounts return 404 in 5.1.x
Summary
A minimal Harper app using the built-in static component returns 404 for static pages mounted under /app in 5.1.17. The same static configuration works in 5.0.32.
This appears to be a regression in the built-in static component when urlPath is configured. Root-mounted static serving still works in 5.1.17.
Versions Tested
- Works:
harper@5.0.32
- Fails:
harper@5.1.17
Minimal Config
static:
files: 'site/**'
urlPath: 'app'
index: true
extensions: ['html']
The same result occurs with:
Expected Behavior
Requests under /app should serve files from site/**, matching 5.0.32 behavior:
/app redirects to /app/
/app/ serves site/index.html
/app/index.html serves site/index.html
/app/search serves site/search.html when extensions: ['html'] is configured
/app/logo.png serves site/logo.png
Actual Behavior In 5.1.17
All /app static requests return 404.
Reproduction
Create a minimal app:
APP=$(mktemp -d)
mkdir -p "$APP/site"
cat > "$APP/package.json" <<'JSON'
{
"name": "harper-static-urlpath-repro",
"version": "1.0.0",
"type": "module"
}
JSON
cat > "$APP/site/index.html" <<'HTML'
<!doctype html>
<title>static repro</title>
<h1>ok</h1>
HTML
cat > "$APP/site/search.html" <<'HTML'
<!doctype html>
<title>search</title>
<h1>search</h1>
HTML
printf 'logo\n' > "$APP/site/logo.png"
cat > "$APP/config.yaml" <<'YAML'
static:
files: 'site/**'
urlPath: 'app'
index: true
extensions: ['html']
YAML
Run the app with an isolated Harper root. Replace the package version to compare:
VERSION=5.1.17
ROOT=$(mktemp -d)
HOME_DIR=$(mktemp -d)
LOG=$(mktemp)
(
cd "$APP" &&
env \
HOME="$HOME_DIR" \
ROOTPATH="$ROOT" \
HDB_ADMIN_USERNAME=admin \
HDB_ADMIN_PASSWORD=password \
NODE_HOSTNAME=localhost \
DEFAULTS_MODE=dev \
npm exec --yes --package "harper@$VERSION" -- harper dev . > "$LOG" 2>&1
) &
PID=$!
for i in $(seq 1 90); do
node -e "fetch('http://127.0.0.1:9926/', { redirect: 'manual' }).then(() => process.exit(0)).catch(() => process.exit(1))" >/dev/null 2>&1 && break
sleep 1
done
node <<'NODE'
const paths = [
'/',
'/index.html',
'/search',
'/search.html',
'/logo.png',
'/app',
'/app/',
'/app/index.html',
'/app/search',
'/app/logo.png',
];
for (const path of paths) {
const response = await fetch(`http://127.0.0.1:9926${path}`, { redirect: 'manual' });
console.log(
path.padEnd(18),
response.status,
(response.headers.get('location') || '').padEnd(8),
response.headers.get('content-type') || ''
);
await response.body?.cancel();
}
NODE
kill -INT "$PID"
wait "$PID"
Observed Results
With harper@5.0.32:
/ 404
/index.html 404
/search 404
/search.html 404
/logo.png 404
/app 301 /app/
/app/ 200 text/html; charset=utf-8
/app/index.html 200 text/html; charset=utf-8
/app/search 200 text/html; charset=utf-8
/app/logo.png 200 image/png
With harper@5.1.17:
/ 404
/index.html 404
/search 404
/search.html 404
/logo.png 404
/app 404
/app/ 404
/app/index.html 404
/app/search 404
/app/logo.png 404
Control case: removing urlPath in 5.1.17 makes root-mounted static serving work:
/ 200 text/html; charset=utf-8
/index.html 200 text/html; charset=utf-8
/search 200 text/html; charset=utf-8
/search.html 200 text/html; charset=utf-8
/logo.png 200 image/png
/app/ 404
/app/index.html 404
Likely Cause
This appears related to the 5.1 routing/middleware changes.
In 5.0.32, the static component registers an HTTP handler with { runFirst: true } and looks up static files using the original request path.
In 5.1.17, component urlPath is injected into server.http routing options. That creates two failure modes:
-
urlPath: app
- Static file entries are indexed under
/app/....
- The route matcher appears to use the raw route
urlPath string, so app does not match request path /app/....
- Result: the static handler is not invoked for
/app/....
-
urlPath: /app
- The route matches
/app/....
- The 5.1 router strips
/app before invoking the static handler.
- The static handler then looks up
/index.html, but entries were indexed as /app/index.html.
- Result: handler runs, but lookup misses and returns 404.
So the issue is not that static serving is generally broken in 5.1.17. It is specifically static.urlPath subpath mounting.
Suggested Fix Direction
The static component probably needs one of:
- opt out of 5.1 prefix stripping,
- index static entries relative to the stripped request path when mounted via
urlPath,
- or have the middleware route matcher and static entry path derivation agree on the same normalized path semantics.
If static.urlPath is no longer intended to mount static files under a URL prefix in 5.1, the migration path should be documented and the old config should fail loudly instead of serving all pages as 404.
static.urlPathsubpath mounts return 404 in 5.1.xSummary
A minimal Harper app using the built-in
staticcomponent returns 404 for static pages mounted under/appin 5.1.17. The same static configuration works in 5.0.32.This appears to be a regression in the built-in
staticcomponent whenurlPathis configured. Root-mounted static serving still works in 5.1.17.Versions Tested
harper@5.0.32harper@5.1.17Minimal Config
The same result occurs with:
Expected Behavior
Requests under
/appshould serve files fromsite/**, matching 5.0.32 behavior:/appredirects to/app//app/servessite/index.html/app/index.htmlservessite/index.html/app/searchservessite/search.htmlwhenextensions: ['html']is configured/app/logo.pngservessite/logo.pngActual Behavior In 5.1.17
All
/appstatic requests return 404.Reproduction
Create a minimal app:
Run the app with an isolated Harper root. Replace the package version to compare:
Observed Results
With
harper@5.0.32:With
harper@5.1.17:Control case: removing
urlPathin 5.1.17 makes root-mounted static serving work:Likely Cause
This appears related to the 5.1 routing/middleware changes.
In 5.0.32, the static component registers an HTTP handler with
{ runFirst: true }and looks up static files using the original request path.In 5.1.17, component
urlPathis injected intoserver.httprouting options. That creates two failure modes:urlPath: app/app/....urlPathstring, soappdoes not match request path/app/..../app/....urlPath: /app/app/..../appbefore invoking the static handler./index.html, but entries were indexed as/app/index.html.So the issue is not that static serving is generally broken in 5.1.17. It is specifically
static.urlPathsubpath mounting.Suggested Fix Direction
The static component probably needs one of:
urlPath,If
static.urlPathis no longer intended to mount static files under a URL prefix in 5.1, the migration path should be documented and the old config should fail loudly instead of serving all pages as 404.