SSR not returning any HTML on server

375 Views Asked by At

I have an Angular application and I just implemented SSR with Angular Universal. Locally I have everything running as expected. No errors on the yarn build:ssr or yarn dev:ssr. When I start the server and inspect the source I get all the meta tags, styling and html.

When I check the source of the page on the server I see some styling:


<!DOCTYPE html><html lang="en"><head>
    <meta charset="utf-8">
    <title>Moviese.at</title>
    <base href="/">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta property="og:title" content="Some movie title">
    <meta property="og:image" content="https://ogp.me/logo.png">
    <link rel="icon" type="image/x-icon" href="favicon.ico">
    <link rel="preconnect" href="https://fonts.gstatic.com">
    <style type="text/css">@font-face{font-family:'Roboto';font-style:normal
    etc...
    <body class="mat-typography">
      <app-root></app-root>
      <script src="runtime.d486b7bb3a252bc1.js" type="module"></script><script src="polyfills.fbae836bfb070669.js" type="module"></script><script src="main.bee0fdd870e27142.js" type="module"></script>
    </body>

I've ran the yarn build:ssr command on the server and yarn serve:ssr and everything is starting without errors.

server.ts:"

import 'zone.js/node';

import { APP_BASE_HREF } from '@angular/common';
import { ngExpressEngine } from '@nguniversal/express-engine';
import * as express from 'express';
import { existsSync, readFileSync } from 'fs';
import { join } from 'path';
import { createWindow } from 'domino';

import { AppServerModule } from './src/main.server';

const scripts = readFileSync('dist/angular-movieseat/browser/index.html').toString();
const window = createWindow(scripts) as Window;
(global as any).window = window;
(global as any).document = window.document;

// The Express app is exported so that it can be used by serverless Functions.
export function app(): express.Express {
  const server = express();
  const distFolder = join(process.cwd(), 'dist/angular-movieseat/browser');
  const indexHtml = existsSync(join(distFolder, 'index.original.html')) ? 'index.original.html' : 'index';

  // Our Universal express-engine (found @ https://github.com/angular/universal/tree/main/modules/express-engine)
  server.engine('html', ngExpressEngine({
    bootstrap: AppServerModule,
  }));

  server.set('view engine', 'html');
  server.set('views', distFolder);

  // Example Express Rest API endpoints
  // server.get('/api/**', (req, res) => { });
  // Serve static files from /browser
  server.get('*.*', express.static(distFolder, {
    maxAge: '1y'
  }));

  // All regular routes use the Universal engine
  server.get('*', (req, res) => {
    res.render(indexHtml, { req, providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }] });
  });

  return server;
}

function run(): void {
  const port = process.env['PORT'] || 4000;

  // Start up the Node server
  const server = app();
  server.listen(port, () => {
    console.log(`Node Express server listening on http://localhost:${port}`);
  });
}

// Webpack will replace 'require' with '__webpack_require__'
// '__non_webpack_require__' is a proxy to Node 'require'
// The below code is to ensure that the server is run only when not requiring the bundle.
declare const __non_webpack_require__: NodeRequire;
const mainModule = __non_webpack_require__.main;
const moduleFilename = mainModule && mainModule.filename || '';
if (moduleFilename === __filename || moduleFilename.includes('iisnode')) {
  run();
}

export * from './src/main.server';

I added this:

const scripts = readFileSync('dist/angular-movieseat/browser/index.html').toString();
const window = createWindow(scripts) as Window;
(global as any).window = window;
(global as any).document = window.document;

To avoid rendering these elements on the server. And finally my Nginx server config:

#http
server {
    listen 80;
    listen [::]:80;
    server_name mo***s.at www.mo***s.at
    return 301 https://$host$request_uri;
}

#https
server {
listen [::]:443 ssl ipv6only=on; # managed by Certbot
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/mo***s.at/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/mo***s.at/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
server_name *.mo***s.at;
access_log /var/log/nginx/nginx.vhost.access.log;
error_log /var/log/nginx/nginx.vhost.error.log;
root /root/angular-mo***se**/dist/angular-mo***se**/browser;
index index.html index.htm;

location / {
        try_files $uri $uri/ /index.html?$args;
        proxy_pass http://localhost:4000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;

}


location /api/ {
    proxy_pass http://localhost:4000/;
  }
location = /favicon.ico {
    access_log off;
    log_not_found off;
  }

  location = /robots.txt {
    access_log off;
    log_not_found off;
  }

  location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
    expires max;
    log_not_found off;
  }
}
1

There are 1 best solutions below

0
Peter Boomsma On

This was quite complex/error prone (at least for me). I'm not that great with server configuration. I googled a bit with regards to Nginx and Angular Universal SSR and I tried a few different Nginx server block configurations until I happened to find one somewhere that worked for me:

upstream m***e.**_upstream_config {
 server 127.0.0.1:4000;
}

server {
 listen 443 ssl http2;
 ssl_certificate /etc/letsencrypt/live/m***e.**/fullchain.pem; # managed by Certbot
 ssl_certificate_key /etc/letsencrypt/live/m***e.**/privkey.pem; # managed by Certbot
 include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
 ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
 server_name www.m***e.**;
 root /root/angular-m***e.**/dist/angular-m***e.**/browser;

 location / {
  try_files $uri $uri @ssr;
 }

 location @ssr {
  proxy_pass http://m***e.**_upstream_config;
  proxy_set_header X-Real-IP $remote_addr;
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  proxy_set_header Host $http_host;
  proxy_http_version 1.1;
  proxy_set_header X-NginX-Proxy true;
  proxy_set_header Upgrade $http_upgrade;
  proxy_set_header Connection "upgrade";
  proxy_cache_bypass $http_upgrade;
  proxy_redirect off;
  proxy_set_header X-Forwarded-Proto $scheme;
 }
}
server {
 listen 80;
 server_name www.m***e.**;
 return 301 https://$server_name$request_uri?;
}

Something else that took me quite a while to figure out was that if you use meta.addTag({ ... }) in your app.component and in another component you also do meta.addTag({ ... }) you get both tags which confused me for quite some time. You want to do meta.updateTag({ ... }) if you just need 1 tag.

To debug my server I used https://github.com/angular-university/angular-universal-course as my clean project.