Charly
Charly

Reputation: 1139

Best Practice: Angular SSR Partial Pre-rendering with dynamic fallback

Background: Using Angular Universal to perform pre-rendering but not all routes will be rendered (query parametered or authenticated-only pages for the most part), so wanting to fallback to the express renderer as needed.

Quick Replication (bash):

npm install -g @angular/cli@next
ng new partial-prerender -s -t --minimal --routing --interactive=false
cd partial-prerender/
ng add @nguniversal/express-engine@'^9.0.0-rc.1'
ng g m child --route child --module app
cat << 'EOF' > src/app/app.component.ts
import { Component } from '@angular/core';

@Component({
  template: `<div [routerLink]="['/']">Root</div><div [routerLink]="['child']">Child</div><router-outlet></router-outlet>`,
})
export class AppComponent {}
EOF
npm run prerender
npm run serve:ssr

This quick replication will produce the app, universal implementation, a child page, and replace the app html to give 2 links and a router outlet, then build/pre-render. Both routes will be pre-rendered, but this is good enough for discussing the issue.

Problem: Dynamic SSR is performed as the Express server will pick up the request rather than serving the pre-rendered static file. URLs are normally accessed without the /index.html specified.

Note the static files can be found at /dist/partial-prerender/browser/index.html and .../child/index.html. For testing, I've replaced the contents of these files with garbage, just to be sure which is being loaded at a glance.

Can also add a console.log('DYNAMIC'); to the server.ts:

server.get('*', (req, res) => {
    console.log('DYNAMIC');
    res.render(indexHtml, { req, providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }] });
});

When making a request to localhost:4000 or localhost:4000/child, the 'DYNAMIC' will be printed and the dynamically rendered version is produced, not giving me my mangled pre-rendered files.

When making a request to localhost:4000/index.html or localhost:4000/child/index.html, the

server.get('*.*', express.static(distFolder, { maxAge: '1y' }));

picks up and serves the mangled files.

All makes sense and why it's happening, but I want to be able to just hit a given url (without the /index.html and receive the pre-rendered files (when available), then fall back to putting SSR to work.


Potential Solution: Modify server.ts to test for file existence matching the given request path + /index.html and serve them, falling back to the res.render(...

If it helps, I produce an alpine-node container and deploy to K8s with an Nginx ingress. Only mention this as maybe there's a magical try-files-like functionality that can be done to 'attempt' a file + /index.html retrieval from the node container, then fallback without the /index.html, but seems highly unlikely.

Upvotes: 4

Views: 4609

Answers (2)

edier_pr
edier_pr

Reputation: 1

npm install @nguniversal/[email protected] --save

It already brings the static page by default.

Upvotes: -1

Gavin Chebor
Gavin Chebor

Reputation: 275

You could use an if statement inside the get request like this

const fullPath = join(distFolder, req.originalUrl);
  if (existsSync(fullPath)) {
    console.log('STATIC Exists');
    return res.sendFile(join(distFolder, req.originalUrl));
  } else {
     //Dynamic
     res.render(indexHtml, { req, providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }] });
   }

Upvotes: 4

Related Questions