Repository navigation
Angular 21.2.0 + @oxc-angular/vite 0.0.7 SSR: AngularNodeAppEngine is unusable (missing app-engine manifest) #60
Description
Activity
I don't think the base plugin should be responsible for wiring up Angular SSR manifests
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.
I've added ssr plugin in #74, you can try it
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
- 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.11vite:8.0.0-beta.16typescript:5.9.3
TypeScript 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
- Wrong root selector in generated Angular SSR app manifest
The OXC-generated Angular app manifest still hardcodes:
<!DOCTYPE html><html><head></head><body><app-root></app-root></body></html>
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:
NG05104- stack included
DefaultDomRenderer2.selectRootElement,locateHostElement,ComponentFactory.create,renderAngular,AngularServerApp.handleRendering
This 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 issue
We still need an app-side shim for
RouterLinkActive.Observed behavior:
linkscan beundefinedduringngAfterContentInitin 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.ngAfterContentInitand initialize:this.links = new QueryList<RouterLink>()
when
linksis missing.- Angular forms SSR parity issues
We still need two forms-related shims.
Observed issues:
NgControlStatuscould be instantiated before a matchingNgControlprovider is available- this produced
NG0201during SSR route rendering FormControlNamecould run_setUpControlbefore the parent form directive was ready
Our workarounds:
- patch
NgControlStatus.ɵfacto injectNgControlwith optional/self-style flags - patch
FormControlName.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;/**
- 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
_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;
}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 (
NG0202class of error)
Our workaround:
- post-process generated
apollo-angularservices and change constructors from:
constructor(apollo: Apollo.Apollo)
to:
constructor(@Inject(Apollo.Apollo) apollo: Apollo.Apollo)
and add:
import { Inject, Injectable } from '@angular/core';
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:<!DOCTYPE html><html><head></head><body><mp-root></mp-root></body></html>
After that manifest override,
AngularNodeAppEnginestarted working for our app.Validation after applying the workarounds
Build:
npm run build:oxc:ssr
Run:
SSR_ENGINE=node NG_ALLOWED_HOSTS=127.0.0.1,3bs.store DISABLE_HTTPS_REDIRECT=true PORT=4215 HOST=127.0.0.1 FEED_REFRESH_INTERVAL_MS=0 node dist/portal-oxc/server/server.mjs
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 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.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:- manifest root-selector override (
app-root-> actual selectormp-root) RouterLinkActivecontent-query shimNgControlStatusDI shimFormControlNamesetup-order shim- Apollo generated-service DI patch with explicit
@Inject(Apollo.Apollo)
Metadata
Metadata
Assignees
Labels
Type
Fields
Priority
Summary
With Angular 21.2.0 and @oxc-angular/vite 0.0.7, OXC SSR build artifacts can be generated, but SSR cannot use
AngularNodeAppEnginefrom@angular/ssr/nodebecause Angular app-engine manifest data is missing in the OXC output.This makes OXC SSR currently dependent on
CommonEnginefallback in our app.Environment
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.7vite:^8.0.0-beta.16typescript:~5.9Reproduction (project-level)
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.