Repository navigation
Conversation
reinabo
reviewed
Oct 6, 2026
Adds tests/bench: an HTTP benchmark of the production Bifrost server against a fixture backend, with wrapped, passthru and native scenarios whose pages are shaped like Alignable's sign-in, business profile and events listing. It reports CPU per request (sampled in the server over IPC), throughput and latency across connection levels, can simulate Rails latency, captures CPU profiles, and checks each response is correct before measuring. Also adds an extractDomElements microbench. The tests/vite server now reads its upstream and public URLs from UPSTREAM_URL and PUBLIC_URL, defaulting to the previous values. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Wrapped pages used to run renderPage twice: once in preHandler only to read the route's config, and again with the backend's HTML. Now the backend request happens inside the one render: the wrapped renderer's +onCreatePageContext calls back into bifrost-fastify, which requests the backend through reply-from and holds the response instead of sending it. If the response is wrappable the render continues with it; otherwise (redirect, non-HTML, no layout) the held response is sent as-is after renderPage returns. buildPageContextInit still re-runs after beforeWrappedRender, as in #71. Vike runs an app's +onCreatePageContext before Bifrost's, so the backend request is memoized per request and exported as loadWrappedPage(): apps whose hook needs the wrapped page, or request state the backend response changes, should await it first. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- An opt-in app-level +onCreatePageContext in tests/vite awaits loadWrappedPage, as Alignable's does. Vike runs it before Bifrost's hook. The test checks it sees the wrapped page and that the fake backend was requested once. The backend now counts requests per page, served at /__hits. - A fake-backend page that drops the connection checks that Bifrost responds with an error instead of hanging. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Vike runs +onCreatePageContext hooks concurrently, not app hooks
first as the earlier commits said. An app hook therefore can't see the
backend's page unless it awaits loadWrappedPage. Instead of requiring
that, Bifrost now renders the page a second time if other
+onCreatePageContext hooks exist and none awaited loadWrappedPage. The
backend is still requested once.
- The memoized loader returns the page's pageContext additions: the
wrapped page plus buildPageContextInit's result after
beforeWrappedRender.
- For the second render, Bifrost's hook throws render(url,
abortReason), and onBeforeRoute adds the page before any hook runs.
Keeping abortReason keeps routes reached by `throw render(url, {
proxy: "wrapped" })` from re-running the page that threw it.
- loadWrappedPage marks the request so Bifrost skips the second render.
It does nothing outside wrapped routes.
The app-hook tests move to their own routes. Together they cover the
paths: Bifrost's hook alone, an app hook that awaits loadWrappedPage,
one that doesn't, and a page that threw render(url, { proxy:
"wrapped" }).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Says what Bifrost is and how requests flow, then gives integration steps: Vike config, Fastify server, the Rails side, and wrapped and passthru routes. Adds a section on awaiting loadWrappedPage in an app's +onCreatePageContext for better wrapped-page performance. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
If getLayout, reading Rails' body, extractDomElements or buildPageContextInit threw while wrapping, Vike rendered its error page, but Bifrost discarded it and sent Rails' page with a 200. That page is missing its layout, because Rails rendered it for wrapping. Bifrost now sends the error page (500, and onError runs), discards Rails' response and strips the layout headers. Also stops adding the proxy headers to the incoming request's headers. That object is pageContext's headersOriginal, so pageContext.headers went stale. rewriteRequestHeaders now adds them to the backend request only. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The first time Bifrost renders a wrapped page twice, it logs which +onCreatePageContext files should await loadWrappedPage. The README now notes that Vike's onCreatePageContext timeout (warning after 4 s, error after 30 s by default) applies to the wait for Rails, and shows how to raise it on wrapped routes. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ses safely A `throw redirect()` from a hook that runs after Bifrost has the backend's response, e.g. a +guard on a wrapped route, was sent with the backend's status (usually 200) if the page was wrappable, or dropped for the backend's response if it wasn't. Bifrost now sends Vike's redirect, with bifrostProxyMode "wrapped" like the error page path. Held responses that aren't sent were destroy()ed. On an unread undici body that emits an error nobody listens for, an uncaught exception that only Vike's global handler keeps from crashing the server, and closes the keep-alive connection to the backend. They're now dumped: undici reads up to 128 KiB so the connection is reused, and closes it for larger pages. A client disconnecting while Bifrost waits for the backend was logged as a server error with a 500. It's now answered with 499. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
dajinchu
force-pushed
the
dc/rails-server-slow-latency
branch
from
October 7, 2026 17:02
54d1769 to
4a5bbaf
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Wrapped pages used to run
renderPagetwice: once inpreHandlerjust to read the route's config, then again with Rails' HTML. Now Rails is requested inside the render, and a wrapped page renders once, which cuts its server CPU by about 30%. It renders a second time only in one case, below.This branch also carries two earlier commits:
0b548f9(Install a single vike and fix hardNavigate return type) is the same commit as in Fix versioning #98. Please merge Fix versioning #98 first; I'll rebase.552183cadds the server benchmarks intests/benchused for the numbers below.How it works
pageContextInit, and the wrapped renderer's new+onCreatePageContextcalls it. The loader requests Rails throughreply-fromand holds the response instead of sending it.<head>/<body>), Rails' response is sent as-is afterrenderPagereturns, as before.buildPageContextInitstill re-runs afterbeforeWrappedRender(run buildPageContextInit again when rendering wrapped pages #71).App
+onCreatePageContexthooksVike runs
+onCreatePageContexthooks concurrently, so an app's hook can't see the wrapped page unless it awaits it. Apps keep working without changes:loadWrappedPage(pageContext)): one render.loadWrappedPage: Bifrost renders the page a second time, so those hooks see it. Its hook throwsrender(url, abortReason)andonBeforeRouteadds the page before any hook runs. KeepingabortReasonkeeps routes reached bythrow render(url, { proxy: "wrapped" })(like Alignable'srenderWrapped) from re-running the page that threw it. Rails is still requested once.loadWrappedPageis exported from@alignable/bifrost, and the README recommends it. In the bench, the second render adds about 0.5 ms of CPU per wrapped page (roughly 20%).For Alignable: wrapped pages work after the bump as-is. To get the single render, add this to
frontend/renderer/+onCreatePageContext.shared.ts:Performance
npm run bench -- -c 1,10, base (552183c) and this branch measured back to back. The bench pages have only Bifrost's hook:Passthru and client navigation still run one
renderPageto read the route's config; that's a follow-up.README
Rewritten to be concise: what Bifrost is, how requests flow, and integration steps (Vike config, Fastify server, the Rails side, wrapped and passthru routes, navigation). It includes the
loadWrappedPageperformance note.Testing
http.spec.tscovers each path. It uses dedicated test routes with an app-level+onCreatePageContext, and checks the number of renders (viapageContextsAborted) and that the backend was requested exactly once:loadWrappedPage: one render;render(url, { proxy: "wrapped" }): exactly two aborts.abortReason, theloadWrappedPagemark).Release
Release both packages together. bifrost-fastify pins
@alignable/bifrostexactly and relies on the new hook.🤖 Generated with Claude Code