Skip to content

feat(platform): integrate experimental streaming SSR - #2531

Closed
benpsnyder wants to merge 4 commits into
analogjs:alphafrom
benpsnyder:feat/streaming-ssr
Closed

benpsnyder wants to merge 4 commits into
analogjs:alphafrom
benpsnyder:feat/streaming-ssr

Conversation

@benpsnyder

@benpsnyder benpsnyder commented Sep 7, 2026 •

Copy link
Copy Markdown
Contributor

PR Checklist

Closes #2526.

This connects the existing experimental streaming renderer to the split Vite/Nitro integration and gives its capture hooks, response body, Angular platform, and browser bootstrap a tested lifecycle. A dashboard can deliver its shell and resolved deferred previews while later data remains pending, then hydrate against the complete authoritative document.

Review scope and shared prerequisites

The streaming implementation remains separate from the other feature PRs. This branch includes the shared prerequisites required to build and test it independently: Node/Nx Agent setup, route policy and SSR entry discovery, platform declarations, router lint setup, navigation tracking, Worker output/bundling, the server-mode optimizer correction, and native request/context and proxy-transform ordering for the deferred server-function example.

These are selected prerequisite fixes, not the full compiler, HTTP, or request/Query lifecycle contributions.

The shared route-policy prerequisite also strips caller-supplied x-analog-no-ssr before internal renderer dispatch. analogjs/analog#2523 owns this repair and its tests. The copied regression verifies that caller headers cannot override configured SSR policy, while request URL/query, method/body, runtime context, and cancellation are preserved.

Affected scope

  • Primary scope: platform.
  • Secondary scopes: router/server streaming and reconciliation, optional server-context fields, the streaming example, documentation, and runtime qualification.

Recommended merge strategy for maintainer [optional]

  • Squash merge
  • Rebase merge
  • Other

What is the new behavior?

Enable analog({ experimental: { streaming: true } }), keep the separate angular() and nitro() plugins, and export renderStream(App, config) from the server entry. @defer blocks with hydration triggers can produce early previews. A route rule such as '/checkout': { streaming: false } retains buffered rendering when the final status must be known before sending headers.

The transform checks the actual Angular defer-function structure using OXC AST positions. It supports symbolic and numeric profiler calls without assuming their ordinal values, is idempotent, and leaves browser/disabled paths alone. The qualified minimum is Angular 21. An absent or unsupported capture primitive retains buffered fallback with diagnostics.

Capture state remains local across overlapping native-async and Zone executions. Completion, failure, cancellation, and disconnected bodies dispose the platform and queued work. The optional host signal and waitUntil keep Worker cleanup reachable; idle comments allow a silent pending render to observe disconnects.

Module assets are preloaded with the shell but execute only after the authoritative body is installed. Failed or truncated documents show a generic non-indexable error view without bootstrapping Angular against a missing root. Preview text is escaped. The HTTP host uses generic error framing after headers are committed; direct renderer callers retain stream errors unless they opt into framing.

Prerendering and crawler responses remain complete buffered documents. Progressive HTTP responses use identity encoding and no-store, no-transform; explicit buffered routes and static assets retain their normal policy.

Test plan

Verified head: dcf5dcf91a93844e37ee5849e2781116396da9f0; current CI checks. Local qualification used Linux, Node 24.15.0, pnpm 10.33.0, and NX_DAEMON=false NX_NO_CLOUD=true. These are recorded executable results; this description update does not change source.

  • pnpm nx test platform: 424 tests pass after the caller-header repair.
  • pnpm nx lint platform, pnpm nx lint analog-app-e2e, and pnpm nx run analog-app-e2e:typecheck pass.
  • Committed-adapter reproduction invokes SSR with and without the caller hint, matching the preserved integration reference. Request regressions cover GET/HEAD/POST, URL/query, body, custom headers, runtime identity, and abort state/reason.
  • Recorded platform/router typecheck and lint qualification passes; router coverage has 401 passing tests.
  • The CI build command completed for 22 projects plus eight prerequisites during local qualification; current-head main and Windows CI also pass.
  • pnpm nx run streaming-app-e2e-playwright:e2e: all three built-example tests pass, including buffered/streamed deferred hydration with caller SSR and streaming hints, the same transferred token, and zero refetch.
  • All eight current-head runtime cases below pass. Both Angular rows run on this head; the Angular 22 job also verifies the deferred server-function example.
Angular / Vite / TypeScript Node 24.15.0 Node + Zone.js Bun 1.4.2 Workerd 1.20260730.1
21.0.0 / 7.0.0 / 5.9.3 Pass Pass (0.15.1) Pass Pass
22.0.0 / 8.0.8 / 6.0.2 Pass Pass (0.16.1) Pass Pass

Workerd qualification uses local Miniflare and the documented runtime compatibility boundary. The streaming policy/prerender tests also verify filtering both caller hints while retaining the configured buffering rules.

  • Changed-file formatting and git diff --check pass.

Does this PR introduce a breaking change?

  • Yes
  • No

Streaming remains experimental and opt-in, and the new ServerContext fields are optional. The shared Module/Durable Worker output correction changes the accidental Pages-shaped dist/_worker.js location: deployments must use Nitro's generated Wrangler configuration and separate public/server directories. Cloudflare Pages retains its existing layout. The streaming guide documents this migration. Angular internal shapes are checked for the recorded versions.

Other information

Workstream 6 of Brandon Roberts' requested split. Compiler environment fixes, Vitest upgrades, buffered SSR/Worker output boundaries, HTTP transport, and request/hydration ownership remain in their prerequisite contributions. This adds streaming SSR, not server components. Assisted by OpenAI Codex.

@coderabbitai

coderabbitai Bot commented Sep 7, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 12 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 8 included reviews currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Team

Run ID: efeac003-1015-432b-ad97-c1d2275eea4f

📥 Commits

Reviewing files that changed from the base of the PR and between 4225f45 and dcf5dcf.

⛔ Files ignored due to path filters (3)
  • .nvmrc is excluded by none and included by none
  • .nx/workflows/agents.yaml is excluded by none and included by none
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml and included by none
📒 Files selected for processing (72)
  • .github/workflows/streaming-runtime.yml
  • .node-version
  • CONTRIBUTING.md
  • apps/analog-app-e2e/tests/ssr-policy.spec.ts
  • apps/analog-app/vite.config.ts
  • apps/docs-analog/src/content/contributing.md
  • apps/docs-analog/src/content/features/server/server-side-rendering.md
  • apps/docs-analog/src/content/features/server/streaming-ssr.md
  • apps/docs-analog/src/content/integrations/tanstack-query/index.md
  • apps/streaming-app-e2e-playwright/project.json
  • apps/streaming-app-e2e-playwright/tests/server-fn-defer-hydrate.spec.ts
  • apps/streaming-app-e2e-playwright/vite.config.ts
  • apps/streaming-app/.gitignore
  • apps/streaming-app/package.json
  • apps/streaming-app/project.json
  • apps/streaming-app/src/app/app.config.server.ts
  • apps/streaming-app/src/server/routes/api/slow-b.get.ts
  • apps/streaming-app/vite.config.ts
  • packages/platform/package.json
  • packages/platform/project.json
  • packages/platform/src/lib/clear-client-page-endpoint.ts
  • packages/platform/src/lib/content/shiki/index.ts
  • packages/platform/src/lib/discover-library-routes.ts
  • packages/platform/src/lib/i18n-extract-plugin.ts
  • packages/platform/src/lib/nitro/analog-nitro-plugin.spec.ts
  • packages/platform/src/lib/nitro/analog-nitro-plugin.ts
  • packages/platform/src/lib/nitro/debug.ts
  • packages/platform/src/lib/nitro/ssr-entry.spec.ts
  • packages/platform/src/lib/nitro/ssr-renderer.spec.ts
  • packages/platform/src/lib/nitro/streaming-renderer.spec.ts
  • packages/platform/src/lib/options.ts
  • packages/platform/src/lib/platform-plugin.spec.ts
  • packages/platform/src/lib/platform-plugin.ts
  • packages/platform/src/lib/route-idiom-diagnostics.ts
  • packages/platform/src/lib/ssr/defer-streaming-build.spec.ts
  • packages/platform/src/lib/ssr/defer-streaming-plugin.spec.ts
  • packages/platform/src/lib/ssr/defer-streaming-plugin.ts
  • packages/platform/src/lib/ssr/ssr-build-plugin.ts
  • packages/platform/src/lib/utils/debug-log-file.ts
  • packages/platform/src/lib/utils/debug.ts
  • packages/platform/tsconfig.lib.json
  • packages/router/eslint.config.mjs
  • packages/router/server/src/defer-reconcile-runtime.spec.ts
  • packages/router/server/src/defer-reconcile-runtime.ts
  • packages/router/server/src/provide-server-context.spec.ts
  • packages/router/server/src/provide-server-context.ts
  • packages/router/server/src/render-stream-navigation.spec.ts
  • packages/router/server/src/render-stream.spec.ts
  • packages/router/server/src/render-stream.ts
  • packages/router/server/src/ssr-navigation.ts
  • packages/router/server/src/utils/ssr-stream-lifecycle.spec.ts
  • packages/router/server/src/utils/ssr-stream-lifecycle.ts
  • packages/router/server/src/utils/stream-request.spec.ts
  • packages/router/server/src/utils/stream-request.ts
  • packages/router/server/src/utils/stream-shell.spec.ts
  • packages/router/server/src/utils/stream-shell.ts
  • packages/router/tokens/src/index.ts
  • packages/vite-plugin-angular/src/lib/angular-build-optimizer-plugin.ts
  • tools/fixtures/streaming-runtime/README.md
  • tools/fixtures/streaming-runtime/index.html
  • tools/fixtures/streaming-runtime/src/app/app.ts
  • tools/fixtures/streaming-runtime/src/app/fast.ts
  • tools/fixtures/streaming-runtime/src/app/probe.ts
  • tools/fixtures/streaming-runtime/src/app/slow.ts
  • tools/fixtures/streaming-runtime/src/main.server.ts
  • tools/fixtures/streaming-runtime/src/main.ts
  • tools/fixtures/streaming-runtime/src/server/routes/api/probe.get.ts
  • tools/fixtures/streaming-runtime/tsconfig.app.json
  • tools/fixtures/streaming-runtime/tsconfig.json
  • tools/fixtures/streaming-runtime/vite.config.ts
  • tools/scripts/streaming-runtime-probes.mjs
  • tools/scripts/streaming-runtime-smoke.mjs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added scope:ci GitHub workflow changes scope:content Changes in @analogjs/content scope:create-analog Changes in create-analog scope:docs Documentation changes scope:nx-plugin Changes in @analogjs/nx-plugin scope:platform Changes in @analogjs/platform scope:repo Repository metadata and tooling scope:router Changes in @analogjs/router scope:storybook-angular Changes in @analogjs/storybook-angular scope:vite-plugin-angular Changes in @analogjs/vite-plugin-angular scope:vitest-angular Changes in @analogjs/vitest-angular labels Sep 7, 2026
@github-actions

github-actions Bot commented Sep 7, 2026 •

Copy link
Copy Markdown

This PR touches multiple package scopes: platform, router, vite-plugin-angular.

Please confirm the changes are closely related. Squash merge is highly preferred. If you recommend a non-squash merge, add a brief note explaining why the commit boundaries matter and why this PR should bypass focused changes per package.

@nx-cloud

nx-cloud Bot commented Sep 7, 2026 •

Copy link
Copy Markdown
Contributor

View your CI Pipeline Execution ↗ for commit 3f4f78d

Command Status Duration Result
nx run-many --target e2e --projects=analog-app-... ✅ Succeeded 1m 35s View ↗
nx run-many --target test ✅ Succeeded 38s View ↗
nx run-many --target build --all --parallel=1 -... ✅ Succeeded 1m 16s View ↗
nx run-many --target lint --all --exclude=conte... ✅ Succeeded 1m 36s View ↗
nx-cloud record -- pnpm prettier:check ✅ Succeeded 13s View ↗

💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗


☁️ Nx Cloud last updated this comment at 2026-09-07 16:12:41 UTC

@netlify

netlify Bot commented Sep 7, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for analog-app ready!

Name Link
🔨 Latest commit dcf5dcf
🔍 Latest deploy log https://app.netlify.com/projects/analog-app/deploys/6a9ee0c794654800083e5600
😎 Deploy Preview https://deploy-preview-2531--analog-app.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify

netlify Bot commented Sep 7, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for analog-blog ready!

Name Link
🔨 Latest commit dcf5dcf
🔍 Latest deploy log https://app.netlify.com/projects/analog-blog/deploys/6a9ee0c832e4d1000849b7f2
😎 Deploy Preview https://deploy-preview-2531--analog-blog.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@nx-cloud nx-cloud Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

At least one additional CI pipeline execution has run since the conclusion below was written and it may no longer be applicable.

Nx Cloud is proposing a fix for your failed CI:

We replaced waitUntil: 'networkidle' with waitUntil: 'load' in apps/analog-app-e2e/tests/page-load-hydration.spec.ts to resolve the playwright/no-networkidle ESLint error introduced by the new test file. This fix is safe because the test's subsequent await expect(page.locator('#greeting')).toHaveText(...) assertions already leverage Playwright's built-in auto-waiting to confirm DOM stability after navigation.

Tip

✅ We verified this fix by re-running analog-app-e2e:lint.

diff --git a/apps/analog-app-e2e/tests/page-load-hydration.spec.ts b/apps/analog-app-e2e/tests/page-load-hydration.spec.ts
index cacc24b5..a75f7211 100644
--- a/apps/analog-app-e2e/tests/page-load-hydration.spec.ts
+++ b/apps/analog-app-e2e/tests/page-load-hydration.spec.ts
@@ -11,7 +11,7 @@ test('hydrates the native load once and shares fresh query loads with getLoadRes
   page.on('pageerror', (error) => errors.push(error.message));
 
   const response = await page.goto('/greet/reader?shout=false', {
-    waitUntil: 'networkidle',
+    waitUntil: 'load',
   });
   expect(response?.status()).toBe(200);
   expect(await response?.text()).toContain('Hello, reader!');

Because this branch comes from a fork, it is not possible for us to apply fixes directly, but you can apply the changes locally using the available options below.

Apply changes locally with:

npx nx-cloud apply-locally Zwjo-hylA

Apply fix locally with your editor ↗   View interactive diff ↗



🎓 Learn more about Self-Healing CI on nx.dev

Rebuild this contribution directly on upstream alpha, keeping prerequisite changes in their own PRs.

Co-Authored-By: OpenAI Codex <noreply@openai.com>
@github-actions github-actions Bot removed scope:content Changes in @analogjs/content scope:create-analog Changes in create-analog scope:nx-plugin Changes in @analogjs/nx-plugin scope:storybook-angular Changes in @analogjs/storybook-angular scope:vite-plugin-angular Changes in @analogjs/vite-plugin-angular scope:vitest-angular Changes in @analogjs/vitest-angular scope:repo Repository metadata and tooling labels Sep 7, 2026
…ites

Qualify independent streaming builds, native context, and deferred function hydration.

Co-Authored-By: OpenAI Codex <noreply@openai.com>
@github-actions github-actions Bot added the scope:vite-plugin-angular Changes in @analogjs/vite-plugin-angular label Sep 7, 2026
Restore caller-header filtering from eeda7af.
Preserve Request adapter fields, runtime context and cancellation; verify
configured SSR rules over built HTTP.

The route-policy contribution owns this shared prerequisite for independent
renderer copies in the split.

Refs: analogjs#2523, analogjs#2516
@benpsnyder
benpsnyder marked this pull request as ready for review September 7, 2026 16:48
brandonroberts added a commit that referenced this pull request Sep 26, 2026
Backport streaming completion, cancellation, hydration, and prerender safeguards from #2531 to beta. Require Vite 6+ for experimental streaming SSR. Refs #2526.
@brandonroberts

Copy link
Copy Markdown
Member

Closing in favor of #2587, which backported the streaming SSR hardening to beta and has been merged: #2587

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

3.0 scope:ci GitHub workflow changes scope:docs Documentation changes scope:platform Changes in @analogjs/platform scope:router Changes in @analogjs/router scope:vite-plugin-angular Changes in @analogjs/vite-plugin-angular

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants