You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
With Angular 21.2.0 and @oxc-angular/vite 0.0.7, OXC SSR build artifacts can be generated, but SSR cannot use AngularNodeAppEngine from @angular/ssr/node because Angular app-engine manifest data is missing in the OXC output.
This makes OXC SSR currently dependent on CommonEngine fallback in our app.
Environment
OS: macOS 15.7.4 (Darwin 24.6.0)
Arch: x86_64 (darwin-x64)
Node: v22.20.0
npm: 10.9.3
Versions
@angular/core: ^21.2.0
@angular/ssr: ^21.2.0
@angular/cli: ^21.2.0
@angular-devkit/build-angular: ^21.2.0
@oxc-angular/vite: ^0.0.7
vite: ^8.0.0-beta.16
typescript: ~5.9
Reproduction (project-level)
Build OXC browser + server bundles:
npm run build:oxc:ssr
Start SSR server with AngularNodeAppEngine path enabled.
Error observed from Angular SSR internals:
txt
Error: Angular app engine manifest is not set. Please ensure you are using the '@angular/build:application' builder to build your server application.
at getAngularAppEngineManifest (...)
at new AngularAppEngine (...)
at new AngularNodeAppEngine (...)
Actual behavior
OXC SSR build completes successfully.
Runtime AngularNodeAppEngine path fails due to missing Angular app-engine manifest.
We had to switch to CommonEngine for OXC SSR runtime.
Expected behavior
Either:
OXC SSR output should include/manually register the manifest required by AngularNodeAppEngine, or
official guidance should clearly state that AngularNodeAppEngine is not currently supported for OXC SSR and CommonEngine is the intended runtime path.
Dist output observation
From OXC SSR artifacts, we get entries like:
dist/.../browser/src/index.oxc.html
dist/.../server/server.mjs
But no Angular app-engine manifest setup compatible with AngularNodeAppEngine.
What we already tried
Upgraded to @oxc-angular/vite@0.0.7 (darwin-x64 binding issue appears resolved).
Kept separate client/server OXC Vite configs.
Forced AngularNodeAppEngine path for verification.
Added fallback to CommonEngine with explicit document resolution.
Added host allowlist handling for SSR to rule out host validation noise.
Verified runtime routes work under CommonEngine (including SSR login callback path).
Additional SSR parity notes (secondary)
Before our app-side shims, we also saw OXC SSR runtime DI/parity issues around:
RouterLinkActive
Forms (NgControlStatus / FormControlName) on SSR route rendering
We can open a separate issue for these if you prefer one issue per problem.
Request
Please confirm:
Is AngularNodeAppEngine support expected with @oxc-angular/vite@0.0.7 + Angular 21.2.0?
If yes, can you share the expected manifest contract/output and recommended config?
If not, please document current SSR support scope and preferred runtime path (CommonEngine only) with known limitations.
I don't think the base plugin should be responsible for wiring up Angular SSR manifests
If the builder is deemed to be the drop-in support for angular builder, and angular by default provides ssr support out of the box, then I guess integrating ssr support in this package is essential.
Update after upgrading and re-testing with newer OXC Angular SSR support.
Current status
This partially fixes the earlier blocker.
With the updated packages below, the OXC SSR server bundle now does emit Angular SSR manifest hooks, so the previous runtime failure:
Angular app engine manifest is not set
is no longer the main blocker.
However, in our app, AngularNodeAppEngine still does not work fully out of the box. We can now get it working only with several app-side patches/workarounds.
Real environment
OS: macOS 15.7.4
Darwin: 24.6.0
Arch: x86_64
Node: v22.22.0
npm: 10.9.4
Versions used for this retest
@angular/core: 21.2.1
@angular/ssr: 21.2.1
@angular/cli: 21.2.1
@angular-devkit/build-angular: 21.2.1
@oxc-angular/vite: 0.0.11
vite: 8.0.0-beta.16
typescript: 5.9.3
TypeScript native preview / tsgo was not used for this SSR reproduction. This was tested with stable typescript@5.9.3.
What is fixed compared to the previous report
The generated OXC SSR bundle now contains Angular manifest registration calls:
setAngularAppManifest(...)
setAngularAppEngineManifest(...)
So this part is improved versus the earlier build where AngularNodeAppEngine could not start because the app-engine manifest was missing.
Remaining bugs / parity issues still requiring fixes upstream
Wrong root selector in generated Angular SSR app manifest
The OXC-generated Angular app manifest still hardcodes:
This caused runtime SSR failure with AngularNodeAppEngine until we patched the manifest ourselves.
Observed runtime failure before the patch:
NG05104
stack included DefaultDomRenderer2.selectRootElement, locateHostElement, ComponentFactory.create, renderAngular, AngularServerApp.handleRendering
This looks like a builder/manifest generation bug. The generated index.server.html asset inside the Angular app manifest must use the real app root selector, not a hardcoded <app-root>.
RouterLinkActive SSR parity issue
We still need an app-side shim for RouterLinkActive.
Observed behavior:
links can be undefined during ngAfterContentInit in the OXC SSR lane
this appears to be a content-query metadata/runtime parity problem in the SSR bundle path
Our workaround was to patch RouterLinkActive.prototype.ngAfterContentInit and initialize:
this.links=newQueryList<RouterLink>()
when links is missing.
Angular forms SSR parity issues
We still need two forms-related shims.
Observed issues:
NgControlStatus could be instantiated before a matching NgControl provider is available
this produced NG0201 during SSR route rendering
FormControlName could run _setUpControl before the parent form directive was ready
Our workarounds:
patch NgControlStatus.ɵfac to inject NgControl with optional/self-style flags
patch FormControlName.prototype._setUpControl to return early if formDirective or addControl is not ready
`import { ɵɵdirectiveInject } from '@angular/core';
import { FormControlName, NgControl, NgControlStatus } from '@angular/forms';
let ngControlStatusShimApplied = false;
let formControlNameShimApplied = false;
/**
OXC build lane workaround:
Some SSR bundles can instantiate NgControlStatus before a matching NgControl
provider is available, which throws NG0201 during route rendering.
*/
export function applyNgControlStatusOxcShim(): void {
if (ngControlStatusShimApplied) return;
ngControlStatusShimApplied = true;
const target = NgControlStatus as unknown as {
ɵfac?: (type?: unknown) => unknown;
};
if (typeof target.ɵfac !== 'function') return;
target.ɵfac = function NgControlStatus_Factory(ngFactoryType?: unknown) {
const Type =
(ngFactoryType as new (cd?: unknown) => unknown) ??
(NgControlStatus as unknown as new (cd?: unknown) => unknown);
return new Type(ɵɵdirectiveInject(NgControl as any, 10 as any));
};
}
/**
OXC build lane workaround:
Some SSR bundles evaluate FormControlName before its parent form directive is
fully wired, causing _setUpControl to throw on a null parent.
*/
export function applyFormControlNameOxcShim(): void {
if (formControlNameShimApplied) return;
formControlNameShimApplied = true;
const prototype = FormControlName.prototype as any;
if (typeof prototype._setUpControl !== 'function') return;
const originalSetUpControl = prototype._setUpControl as () => void;
prototype._setUpControl = function patchedSetUpControl(this: any) {
if (
!this.formDirective ||
typeof this.formDirective.addControl !== 'function'
) {
return;
}
return originalSetUpControl.call(this);
};
}
`
Apollo Angular generated service DI metadata issue in OXC SSR lane
We also still need a generated-code patch for Apollo Angular services.
Observed behavior before the patch:
generated GraphQL services could fail DI in the OXC SSR build
we saw the generated factory path degrade to ɵɵinvalidFactoryDep(...)
runtime symptom was Angular DI failure (NG0202 class of error)
Our workaround:
post-process generated apollo-angular services and change constructors from:
So this looks like another OXC SSR metadata / DI parity problem, not an Apollo transport/link issue.
Exact local workaround needed to make AngularNodeAppEngine work
We had to override Angular’s app manifest before creating the request engine, replacing the generated index.server.html asset with one that uses the real selector:
GET /en/account/login?cburl=%2Fen%2Fmerchant%2Fdashboard -> 200
GET /en/women/all -> 200
GET /en/cart -> 302 to /en/account/login?cburl=%2Fen%2Fcart which is expected app behavior
The returned HTML is real SSR output with <mp-root ...> and rendered page content, not just a server startup success.
Summary for maintainers
@oxc-angular/vite@0.0.11 is a real improvement because Angular app-engine manifests are now emitted.
But for this Angular 21.2.1 app, SSR with AngularNodeAppEngine is still not production-ready out of the box because we still need all of the following local patches:
manifest root-selector override (app-root -> actual selector mp-root)
RouterLinkActive content-query shim
NgControlStatus DI shim
FormControlName setup-order shim
Apollo generated-service DI patch with explicit @Inject(Apollo.Apollo)
Activity
brandonroberts commentedon Mar 4, 2026
I don't think the base plugin should be responsible for wiring up Angular SSR manifests
nkrass commentedon Mar 6, 2026
If the builder is deemed to be the drop-in support for angular builder, and angular by default provides ssr support out of the box, then I guess integrating ssr support in this package is essential.
Brooooooklyn commentedon Mar 6, 2026
I've added ssr plugin in #74, you can try it
nkrass commentedon Mar 7, 2026
Update after upgrading and re-testing with newer OXC Angular SSR support.
Current status
This partially fixes the earlier blocker.
With the updated packages below, the OXC SSR server bundle now does emit Angular SSR manifest hooks, so the previous runtime failure:
Angular app engine manifest is not setis no longer the main blocker.
However, in our app,
AngularNodeAppEnginestill does not work fully out of the box. We can now get it working only with several app-side patches/workarounds.Real environment
Versions used for this retest
@angular/core:21.2.1@angular/ssr:21.2.1@angular/cli:21.2.1@angular-devkit/build-angular:21.2.1@oxc-angular/vite:0.0.11vite:8.0.0-beta.16typescript:5.9.3TypeScript native preview /
tsgowas not used for this SSR reproduction. This was tested with stabletypescript@5.9.3.What is fixed compared to the previous report
The generated OXC SSR bundle now contains Angular manifest registration calls:
setAngularAppManifest(...)setAngularAppEngineManifest(...)So this part is improved versus the earlier build where
AngularNodeAppEnginecould not start because the app-engine manifest was missing.Remaining bugs / parity issues still requiring fixes upstream
The OXC-generated Angular app manifest still hardcodes:
But our actual root component selector is:
selector: 'mp-root'This caused runtime SSR failure with
AngularNodeAppEngineuntil we patched the manifest ourselves.Observed runtime failure before the patch:
NG05104DefaultDomRenderer2.selectRootElement,locateHostElement,ComponentFactory.create,renderAngular,AngularServerApp.handleRenderingThis looks like a builder/manifest generation bug. The generated
index.server.htmlasset inside the Angular app manifest must use the real app root selector, not a hardcoded<app-root>.RouterLinkActiveSSR parity issueWe still need an app-side shim for
RouterLinkActive.Observed behavior:
linkscan beundefinedduringngAfterContentInitin the OXC SSR laneOur workaround was to patch
RouterLinkActive.prototype.ngAfterContentInitand initialize:when
linksis missing.We still need two forms-related shims.
Observed issues:
NgControlStatuscould be instantiated before a matchingNgControlprovider is availableNG0201during SSR route renderingFormControlNamecould run_setUpControlbefore the parent form directive was readyOur workarounds:
NgControlStatus.ɵfacto injectNgControlwith optional/self-style flagsFormControlName.prototype._setUpControlto return early ifformDirectiveoraddControlis not ready`import { ɵɵdirectiveInject } from '@angular/core';
import { FormControlName, NgControl, NgControlStatus } from '@angular/forms';
let ngControlStatusShimApplied = false;
let formControlNameShimApplied = false;
/**
*/
export function applyNgControlStatusOxcShim(): void {
if (ngControlStatusShimApplied) return;
ngControlStatusShimApplied = true;
const target = NgControlStatus as unknown as {
ɵfac?: (type?: unknown) => unknown;
};
if (typeof target.ɵfac !== 'function') return;
target.ɵfac = function NgControlStatus_Factory(ngFactoryType?: unknown) {
const Type =
(ngFactoryType as new (cd?: unknown) => unknown) ??
(NgControlStatus as unknown as new (cd?: unknown) => unknown);
return new Type(ɵɵdirectiveInject(NgControl as any, 10 as any));
};
}
/**
_setUpControlto throw on a null parent.*/
export function applyFormControlNameOxcShim(): void {
if (formControlNameShimApplied) return;
formControlNameShimApplied = true;
const prototype = FormControlName.prototype as any;
if (typeof prototype._setUpControl !== 'function') return;
const originalSetUpControl = prototype._setUpControl as () => void;
prototype._setUpControl = function patchedSetUpControl(this: any) {
if (
!this.formDirective ||
typeof this.formDirective.addControl !== 'function'
) {
return;
}
};
}
`
We also still need a generated-code patch for Apollo Angular services.
Observed behavior before the patch:
ɵɵinvalidFactoryDep(...)NG0202class of error)Our workaround:
apollo-angularservices and change constructors from:to:
and add:
So this looks like another OXC SSR metadata / DI parity problem, not an Apollo transport/link issue.
Exact local workaround needed to make AngularNodeAppEngine work
We had to override Angular’s app manifest before creating the request engine, replacing the generated
index.server.htmlasset with one that uses the real selector:After that manifest override,
AngularNodeAppEnginestarted working for our app.Validation after applying the workarounds
Build:
Run:
Requests tested:
GET /en->200GET /en/account/login?cburl=%2Fen%2Fmerchant%2Fdashboard->200GET /en/women/all->200GET /en/cart->302to/en/account/login?cburl=%2Fen%2Fcartwhich is expected app behaviorThe returned HTML is real SSR output with
<mp-root ...>and rendered page content, not just a server startup success.Summary for maintainers
@oxc-angular/vite@0.0.11is a real improvement because Angular app-engine manifests are now emitted.But for this Angular 21.2.1 app, SSR with
AngularNodeAppEngineis still not production-ready out of the box because we still need all of the following local patches:app-root-> actual selectormp-root)RouterLinkActivecontent-query shimNgControlStatusDI shimFormControlNamesetup-order shim@Inject(Apollo.Apollo)