I have an Angular 16 application with SSR (Server-Side Rendering) that uses Koa as the Node framework for the server. To make it work, I've created my own engine.
export function ngKoaEngine(setupOptions: Readonly<NgSetupOptions>) {
const engine = new CommonEngine(setupOptions.bootstrap, setupOptions.providers);
return async (filePath: string, ctx: Context, options: Readonly<Partial<CommonRenderOptions>> = {}) => {
const moduleOrFactory = options.bootstrap || setupOptions.bootstrap;
if (!moduleOrFactory) {
throw new Error('You must pass in a NgModule to be bootstrapped');
}
const { request } = ctx;
const renderOptions: CommonRenderOptions = Object.assign({ bootstrap: setupOptions.bootstrap }, options);
renderOptions.url = renderOptions.url || `${request.protocol}://${request.get('host') || ''}${request.originalUrl}`;
renderOptions.documentFilePath = renderOptions.documentFilePath || filePath;
renderOptions.providers = (renderOptions.providers || []).concat(getReqResProviders(ctx));
renderOptions.publicPath = renderOptions.publicPath ?? setupOptions.publicPath ?? (options as any).settings?.views;
renderOptions.inlineCriticalCss = renderOptions.inlineCriticalCss ?? setupOptions.inlineCriticalCss;
const html = await engine.render(renderOptions);
ctx.body = html;
};
}
/**
* Get providers of the request and response
*/
function getReqResProviders(ctx: Context): StaticProvider[] {
const providers: StaticProvider[] = [
{ provide: CONTEXT, useValue: ctx },
{ provide: REQUEST, useValue: ctx.request },
];
if (ctx.response) {
providers.push({ provide: RESPONSE, useValue: ctx.response });
}
return providers;
}
And in my main server file, I have something like this:
const render = ngKoaEngine({
inlineCriticalCss: false,
bootstrap: AppServerModule,
});
server.use(async (ctx) => {
try {
await render(indexPath, ctx);
} catch (error) {
ctx.status = httpStatusCodes.HTTP_INTERNAL_SERVER_ERROR;
ctx.body = error.stack + error.message;
}
});
and this main router
{
path: '**',
redirectTo: 'error.html',
},
{
path: 'error.html',
loadChildren: () => import('./not-found/not-found.module').then((mod) => mod.NotFoundModule),
},
I need that when navigating to the error.html route, a notFoundComponent is rendered, which is done correctly, but it returns a status 200 when I need a 404.
Following what was indicated here Angular 9 SSR 404 Not Found Page with Status code, I have tried modifying my notFound component as follows:
export class NotFoundComponent implements OnInit {
constructor(
@Optional() @Inject(RESPONSE) private response: Response,
@Inject(PLATFORM_ID) private platformId,
) {}
ngOnInit(): void {
if (isPlatformServer(this.platformId)) {
this.response.status = 404;
}
}
}
In this way, it returns a 404 but does not render the notFound page; instead, it returns
{
code: "404",
description: "Resource not found",
level: "ERROR",
message: "Route not found [ROUTE_NOT_FOUND_ERROR]"
}
If instead of setting this.response.status = 404`` , I set this.response.status = 400`, the notFound page is rendered, and it returns a 400 status. However, when I set it to 404, the page doesn't render.
I have tried returning the status in the ngKoaEngine, but every time I set it to 404, it doesn't render. Is there a way to achieve both, rendering the notFound page and having the status set to 404?