-
Notifications
You must be signed in to change notification settings - Fork 11.9k
Description
Description
When creating a new Angular 21 project with SSR and prerendering enabled, the returned HTML from the server does not include the prerendered content. Instead, it returns the client-side rendered HTML (index.csr.html). The fully prerendered index.html is not being served.
Steps to reproduce
- Run ng new
scaffolding-ng21with SSR and prerendering enabled. - Build the project:
ng build - Serve SSR:
npm run serve:ssr:scaffolding-ng21 - Open http://localhost:4000 in a browser and inspect the network response.
Expected behavior
The returned HTML should be the prerendered index.html generated by Angular, containing the fully rendered content of the AppComponent from the scaffolding template.
Actual behavior
The server returns the client-side rendered HTML (index.csr.html) instead of the prerendered HTML, resulting in an empty shell and missing prerendered content.
Additional context
app.routes.server.ts:
import { RenderMode, ServerRoute } from '@angular/ssr';
export const serverRoutes: ServerRoute[] = [
{
path: '**',
renderMode: RenderMode.Prerender
}
];
package.json:
{
"name": "scaffolding-ng21",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development",
"test": "ng test",
"serve:ssr:scaffolding-ng21": "node dist/scaffolding-ng21/server/server.mjs"
},
"private": true,
"packageManager": "npm@10.9.2",
"dependencies": {
"@angular/common": "^21.2.0",
"@angular/compiler": "^21.2.0",
"@angular/core": "^21.2.0",
"@angular/forms": "^21.2.0",
"@angular/platform-browser": "^21.2.0",
"@angular/platform-server": "^21.2.0",
"@angular/router": "^21.2.0",
"@angular/ssr": "^21.2.1",
"express": "^5.1.0",
"rxjs": "~7.8.0",
"tslib": "^2.3.0"
},
"devDependencies": {
"@angular/build": "^21.2.1",
"@angular/cli": "^21.2.1",
"@angular/compiler-cli": "^21.2.0",
"@types/express": "^5.0.1",
"@types/node": "^20.17.19",
"jsdom": "^28.0.0",
"prettier": "^3.8.1",
"typescript": "~5.9.2",
"vitest": "^4.0.8"
}
}
default/scaffolding server.ts:
import {
AngularNodeAppEngine,
createNodeRequestHandler,
isMainModule,
writeResponseToNodeResponse,
} from '@angular/ssr/node';
import express from 'express';
import { join } from 'node:path';
const browserDistFolder = join(import.meta.dirname, '../browser');
const app = express();
const angularApp = new AngularNodeAppEngine();
/**
* Example Express Rest API endpoints can be defined here.
* Uncomment and define endpoints as necessary.
*
* Example:
* ```ts
* app.get('/api/{*splat}', (req, res) => {
* // Handle API request
* });
* ```
*/
/**
* Serve static files from /browser
*/
app.use(
express.static(browserDistFolder, {
maxAge: '1y',
index: false,
redirect: false,
}),
);
/**
* Handle all other requests by rendering the Angular application.
*/
app.use((req, res, next) => {
angularApp
.handle(req)
.then((response) =>
response ? writeResponseToNodeResponse(response, res) : next(),
)
.catch(next);
});
/**
* Start the server if this module is the main entry point, or it is ran via PM2.
* The server listens on the port defined by the `PORT` environment variable, or defaults to 4000.
*/
if (isMainModule(import.meta.url) || process.env['pm_id']) {
const port = process.env['PORT'] || 4000;
app.listen(port, (error) => {
if (error) {
throw error;
}
console.log(`Node Express server listening on http://localhost:${port}`);
});
}
/**
* Request handler used by the Angular CLI (for dev-server and during build) or Firebase Cloud Functions.
*/
export const reqHandler = createNodeRequestHandler(app);
Proof that index.html was successfully pre-rendered:
Proof that server responded with the contents of index.csr.html instead of the pre-rendered index.html that should contain the complete home page:
