When working on documentation for https://github.com/libfn/functional (published at https://libfn.org ) I have encountered a bug where a page would be redirected like so:
$ curl -v -sL "https://libfn.org/type-algebra/index"
* Host libfn.org:443 was resolved.
. . .
> GET /type-algebra/index HTTP/2
> Host: libfn.org
> User-Agent: curl/8.18.0
> Accept: */*
>
. . .
< HTTP/2 200
< server: GitHub.com
< content-type: text/html; charset=utf-8
. . .
< vary: Accept-Encoding
< x-fastly-request-id: 4c1cc053d7ce9f9d87ddf901dd6ddd1f406afe0d
< content-length: 201
<
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="refresh" content="0; url=//type-algebra/index">
<title>redirecting...</title>
</head>
<body>
</body>
* Connection #0 to host libfn.org:443 left intact
I asked Claude to help me fix it - we already had similar fixes in place for the navigation between pages. He diagnosed a general cause in Znai, so I asked it to also file an issue here - which you can read below.
Please do ping me if you need any help.
Setup: a znai site deployed at the root of its own domain (GitHub Pages behind a custom domain), so there is no path prefix to give: znai --deploy <dir> --doc-id "". Observed with znai 1.91; 1.73 behaved identically.
What happens: internal URLs are composed as /<doc-id>/<chapter>/<page>, so with an empty doc-id they come out as //chapter/page — a network-path (protocol-relative) reference. The browser resolves //type-algebra/index as a host named type-algebra and leaves the site to a DNS error. All four markdown spellings of an internal link (chapter/page, chapter/page/, ../chapter/page/, page.md) emit the identical doubled URL, so no document spelling avoids it.
Three surfaces in the deployed tree carry the doubled URLs:
- Links written in page bodies:
href="//chapter/page". (The left navigation is unaffected — React handles its clicks without following the href — which is why this is easy to miss.)
- The chapter index redirect stubs (
generating chapter index redirect pages during export), at <chapter>/index.html:
<meta http-equiv="refresh" content="0; url=//type-algebra/index">
search-entries.xml:
<url>//readme/index</url>
Live example at the time of filing (the GitHub Pages site of https://github.com/libfn/functional):
$ curl -s https://libfn.org/type-algebra/index | grep url=
<meta http-equiv="refresh" content="0; url=//type-algebra/index">
A related subtlety in the redirect stubs, worth fixing in the same area: the stub file <chapter>/index.html and the real page directory <chapter>/index/index.html collide at the extension-less URL /<chapter>/index. GitHub Pages resolves the file before the directory, so once the doubled slash is repaired, the stub redirects to a URL that serves the stub again — an infinite meta-refresh loop. A trailing slash in the stub target (url=/<chapter>/index/) routes to the real page on such hosts and is harmless elsewhere.
Suggestion: treat an empty doc-id as "deployed at the root" and emit single-slash URLs (or provide an explicit url-base style option), and give the redirect stubs trailing-slash targets.
Workaround we run today: post-processing the deployed tree with https://github.com/libfn/functional/blob/main/scripts/fix_site_urls.py — collapsing the doubled slashes in pages; we are extending it to the redirect stubs and the search entries.
Assisted-by: Claude:claude-fable-5
When working on documentation for https://github.com/libfn/functional (published at https://libfn.org ) I have encountered a bug where a page would be redirected like so:
I asked Claude to help me fix it - we already had similar fixes in place for the navigation between pages. He diagnosed a general cause in Znai, so I asked it to also file an issue here - which you can read below.
Please do ping me if you need any help.
Setup: a znai site deployed at the root of its own domain (GitHub Pages behind a custom domain), so there is no path prefix to give:
znai --deploy <dir> --doc-id "". Observed with znai 1.91; 1.73 behaved identically.What happens: internal URLs are composed as
/<doc-id>/<chapter>/<page>, so with an empty doc-id they come out as//chapter/page— a network-path (protocol-relative) reference. The browser resolves//type-algebra/indexas a host namedtype-algebraand leaves the site to a DNS error. All four markdown spellings of an internal link (chapter/page,chapter/page/,../chapter/page/,page.md) emit the identical doubled URL, so no document spelling avoids it.Three surfaces in the deployed tree carry the doubled URLs:
href="//chapter/page". (The left navigation is unaffected — React handles its clicks without following the href — which is why this is easy to miss.)generating chapter index redirect pagesduring export), at<chapter>/index.html:search-entries.xml:Live example at the time of filing (the GitHub Pages site of https://github.com/libfn/functional):
A related subtlety in the redirect stubs, worth fixing in the same area: the stub file
<chapter>/index.htmland the real page directory<chapter>/index/index.htmlcollide at the extension-less URL/<chapter>/index. GitHub Pages resolves the file before the directory, so once the doubled slash is repaired, the stub redirects to a URL that serves the stub again — an infinite meta-refresh loop. A trailing slash in the stub target (url=/<chapter>/index/) routes to the real page on such hosts and is harmless elsewhere.Suggestion: treat an empty doc-id as "deployed at the root" and emit single-slash URLs (or provide an explicit url-base style option), and give the redirect stubs trailing-slash targets.
Workaround we run today: post-processing the deployed tree with https://github.com/libfn/functional/blob/main/scripts/fix_site_urls.py — collapsing the doubled slashes in pages; we are extending it to the redirect stubs and the search entries.
Assisted-by: Claude:claude-fable-5