Skip to content

HTTP transport, CLI, and redirects as host rules - #1

Merged
ryansolid merged 3 commits into
mainfrom
http-transport-cli-redirects
Sep 6, 2026
Merged

HTTP transport, CLI, and redirects as host rules#1
ryansolid merged 3 commits into
mainfrom
http-transport-cli-redirects

Conversation

@ryansolid

Copy link
Copy Markdown
Member

Two of the gaps between what the README claims and what was usable outside Vite + Solid.

Transports and CLI

  • httpTransport(target, { headers?, fetch? }) — prerender a running server (preview server, container, staging deploy). Requests are re-addressed to the target's origin; redirects come back as the server's 3xx so the engine handles them.
  • moduleTransport(entry) / loadHandler(entry) — import a handleRequest / fetch / default.fetch module. The Vite plugin now uses this instead of its private copy.
  • npx prerender-crawler <target> --out <dir> over both, with seeds, mode, concurrency, interval, retries, --no-links, --redirects, --continue, --flat.

Redirects

Previously an internal redirect wrote the destination's full HTML under the old path and never rendered the destination at its own URL unless linked — duplicate content with no canonical, and a correctness gap on static hosts (the crawl followed the redirect; production had no rule for it).

Now a 3xx path is a RedirectRecord (one per hop) and its target is crawled as a page of its own; the old path gets a meta-refresh stub pointing at the chain's end. Same-page respellings (/posts → /posts/, what static servers do) are followed in place. redirects() emits a _redirects file and declares handlesRedirects, which suppresses stubs — Cloudflare applies rules regardless of files but Netlify lets a file shadow an unforced rule (and rejects Cloudflare-incompatible !), so no stub is the one configuration that works on both.

maxRedirects is removed (nothing left to bound). PrerenderContext gains live pages / redirects. Minor bump.

Verified beyond the unit tests: the built bin against the example's dist/server/server.js (in-process) and against a static file server over HTTP (trailing-slash redirects followed in place, nothing spuriously recorded).

Made with Cursor

ryansolid and others added 3 commits September 5, 2026 22:09
A 3xx path used to be followed in place: the destination's full HTML was
written under the OLD path (duplicate content, no canonical) and the
destination itself was never rendered at its own URL unless linked.

Now a redirect is a record ({ from, to, status }, one per hop) and its
same-origin target enters the queue as a page in its own right. The
redirected path gets a meta-refresh stub pointing at the chain's final
destination. A redirect to another spelling of the same page
(/posts -> /posts/) is followed in place, not recorded. Cycles terminate
because each path is crawled once; maxRedirects has nothing left to bound
and is removed.

redirects() integration emits a _redirects rules file (Netlify /
Cloudflare Pages) and declares handlesRedirects, which stops the engine
writing stubs: Cloudflare applies rules regardless of files, but Netlify
lets an existing file shadow an unforced rule. redirectStubs controls
stubs directly. PrerenderContext exposes live pages and redirects views.

Co-authored-by: Cursor <cursoragent@cursor.com>
httpTransport(target) re-addresses the crawl's requests to a running
server and hands redirects back unfollowed; moduleTransport(entry) /
loadHandler(entry) import a handleRequest / fetch / default.fetch module
(the Vite plugin now uses it). The engine's Transport contract was always
the agnostic pitch; these make it true outside Vite.

prerender-crawler <target> --out <dir>: a CLI over both transports with
seeds, mode, concurrency, interval, retries, link following, redirect
handling and --continue. For an HTTP target the crawl origin is the
target's so absolute links count as same-origin.

Co-authored-by: Cursor <cursoragent@cursor.com>
interval spaced the slots workers claimed on a shared timeline. A start
whose timer fired late (busy event loop — CI, or a heavy render) could be
followed by the next worker's on-time slot less than interval later. Now a
worker also holds until interval has passed since the last ACTUAL start,
and the timeline moves to that start. Monotonic clock throughout; the
test that measured with performance.now against a Date.now pacer no
longer disagrees with itself by a millisecond.

Co-authored-by: Cursor <cursoragent@cursor.com>
@ryansolid
ryansolid merged commit 3d82767 into main Sep 6, 2026
4 checks passed
@ryansolid
ryansolid deleted the http-transport-cli-redirects branch September 6, 2026 05:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant