Skip to content

Empty doc-id emits protocol-relative internal URLs (//chapter/page) in page links, redirect stubs and search-entries.xml #1575

Description

@Bronek

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:

  1. 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.)
  2. 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">
  3. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions