How to deploy an Angular 4 app with server-side rendering on prod server

14.9k Views Asked by At

I've been looking for about 3 hours for hosting an Angular 4 app on a server with "server-side rendering" enabled. For note - I have a AWS server which has Apache installed (Ubuntu).

First of all, I already know how can we host an Angular 4 app (without server-side rendering). But here my major concern is that I want to enable my app to be enabled for - server-side rendering.

In my local, I use npm start command, which automatically serves the app (with server-side rendering enabled) on - http://localhost:4000

My package.json file looks like this:

...
"scripts": {
    "serve": "ng serve",
    "prestart": "ng build --prod && ngc && webpack",
    "start": "node dist/server.js",
    "build": "ng build"
},
...

These all commands are working fine. But I'm confused that should I again run

npm start

on a production server, so that it also requires node_modules to install. Which doesn't seems the right way to me?

Can anyone please help me with hosting my app with "server-side rendering" enabled.

4

There are 4 best solutions below

6
On

Can anyone please help me with hosting my app with "server-side rendering" enabled.

Yes. Unfortunately, I use Nginx. However, the approach shouldn't be any different in Apache or whatnot.

So this is how I am hosting my Server Side Rendering Angular application in production (I'm on DO). I wrote an article about it on my blog:

After building your SSR, you should have something like this:

enter image description here

After you managed to send everything within the dist/ folder onto your remote server, you run it using pm2 (which is what I use to run my node apps)

Run pm2 start path/to/dist/server.js --name name_of_process

It should be running on localhost:4000

The Nginx virtual server block below should get all requests proxied through your Nginx to the Server Side Rendering Angular application.

The nginx conf below actually serves both the statics of your angular SSR, and falls back to the proxy when request isn't for a static file.

upstream ssr_khophi_nodejs {
    server 127.0.0.1:4000;
}

server {
    listen 443 ssl http2;

    server_name staging.khophi.com; # <--- Change this part

    include /etc/nginx/ssl/ssl-params.conf;# <--- Ignore this part if not using SSL
    include /etc/nginx/ssl/khophi.com.ssl;# <--- Ignore this part if not using SSL

    root /home/khophi/khophi.com/ssr/dist/browser; # <-- Notice where we're point to

   location / {
        try_files $uri $uri @backend; # <--- This looks for requests (statics i.e js/css/fonts)
                                      # in /ssr/dist/browser folder. If nothing found, calls @backend
    }

    location @backend {
        # NOTE THERE IS NO TRAILING SLASH AT THE END. NO TRAILING SLASH. NO SLASH. NO!
        proxy_pass http://ssr_khophi_nodejs; # <--- THIS DOES NOT HAVE A TRAILING '/'
        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 staging.khophi.com;
    return 301 https://$server_name$request_uri?;
}

I hope this helps you.

1
On

Actually it's very simple.

Use a shell to go to the directory of your angular 4 project. and do this:

$ ng build --prod

This will compile the application and the result is in the dist folder at the root of your angular application.

Now you just have to put the content of the dist folder in a HTTP server and eat popcorn.

Cdly, Jerry Yong-busson.

1
On

Ok, i'm sorry i miss read your question :).

First of all, Apache is no more required to serve your application. This is because node.js will serve it for you.

So if apache service is no required on your server, you should disable it for security questions and probably to avoid port conflict between apps.

Server rendering will required to modify your angular app. Let's start.

1) Go to your angular project and install those package:

npm i -S @angular/platform-server @angular/animations express

2) Open src/app/app.module.ts and modify the commented line below with your app name.

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule.withServerTransition({ appId: 'your-angular-app-name' }), // You just have to change this line
    FormsModule,
    HttpModule,
    AppRoutingModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})

3) Now, we'll set the server in your app. For this, create the file app/src/app.server.module.ts and at the following code.

import { NgModule } from '@angular/core';
import { ServerModule } from '@angular/platform-server';
import { AppModule } from './app.module';
import { AppComponent } from './app.component';

@NgModule({
imports: [
    ServerModule, // Load the servermodule
    AppModule     // Load the modules of AppModule
],
bootstrap: [AppComponent]
})
export class AppServerModule { }

4) Create the file src/server.ts and add the following code. This is the node server.

import 'reflect-metadata';
import 'zone.js/dist/zone-node';
import { platformServer, renderModuleFactory } from '@angular/platform-server';
import { enableProdMode } from '@angular/core';
import { AppServerModuleNgFactory } from '../dist/ngfactory/src/app/app.server.module.ngfactory';
import * as express from 'express';
import { readFileSync } from 'fs';
import { join } from 'path';
const PORT = 4000;
enableProdMode();
const app = express();
let template = readFileSync(join(__dirname, '..', 'dist', 'index.html')).toString();
app.engine('html', (_, options, callback) => {
  const opts = { document: template, url: options.req.url };
  renderModuleFactory(AppServerModuleNgFactory, opts)
    .then(html => callback(null, html));
});
app.set('view engine', 'html');
app.set('views', 'src');
app.get('*.*', express.static(join(__dirname, '..', 'dist')));
app.get('*', (req, res) => {
  res.render('index', { req });
});
app.listen(PORT, () => {
  console.log(`Listening on http://localhost:${PORT}...`);
});

5) Open src/tsconfig.app.json and add the file server.ts to the exclude json variable.

{
  "extends": "../tsconfig.json",
  "compilerOptions": {
    "outDir": "../out-tsc/app",
    "module": "es2015",
    "baseUrl": "",
    "types": []
  },
  "exclude": [
    "server.ts", # Just here !
    "test.ts",
    "**/*.spec.ts"
  ]
}

6) Open tsconfig.json locate at the root of your project and add the angularCompilerOptions like this:

{
  "compileOnSave": false,
  "compilerOptions": {
    "outDir": "./dist/out-tsc",
    "baseUrl": "src",
    "sourceMap": true,
    "declaration": false,
    "moduleResolution": "node",
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "target": "es5",
    "typeRoots": [
      "node_modules/@types"
    ],
    "lib": [
      "es2016",
      "dom"
    ]
  },
  "angularCompilerOptions": { 
    "genDir": "./dist/ngfactory",
    "entryModule": "./src/app/app.module#AppModule"
  }  
}

7) Now, modify your package.json to make your life easier.

{
  "scripts": {
    "prestart": "ng build --prod && ngc",
    "start": "ts-node src/server.ts", 
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e"
  },
}

8) Now you can test your app with:

npm start

I hope this will help you.

NOTE: If your project already exist, you'll probably have some problems with some modules like Angular Material because node.js doesn't know, window object, documents object.

So, you'll have to:

  • Create app.common.module.ts, whose the other module loading file will inherit. And gather common module (client+server).

  • Finally put the modules which annoying us in the app.module.ts .

Cdly.

0
On

no 7 package.jsno script has duplicate start script.

Also we need server support which does not included on a shared hosting account.