Root path not rendering properly Angular Universal

Issue

When I try to access ‘https://mywebsite.com‘ or ‘localhost:7070’, the Universal App should serve the page defined by Angular Router instead of the original index.html empty page.

The Universal App is rendering all other routes properly if I try to access them directly, for example ‘https://mywebsite.com/contact‘, even for lazy loaded modules and deep nested child routes, but when I try to access the simplest one, the root url of my website, the Universal App seems to not be able to build the initial page defined by my Angular Router.

This is the configuration of my server routing module:

const routes: Routes = [
  {
    path: '',
    component: OutletComponent,
    children: [
      {
        path: '',
        component: HomeComponent
      },
      {
        path: 'contact',
        component: ContactComponent
      },
       {
        path: 'inexistent-page',
        component: InexistentPageComponent
      },
      {
        path: '**',
        redirectTo: 'inexistent-page'
      }
    ]
  }
]

@NgModule({
  declarations: [
    HomeComponent,
    ContactComponent,
    InexistentPageComponent
  ],
  imports: [
    RouterModule.forRoot(routes, {
      paramsInheritanceStrategy: 'always',
      initialNavigation: 'enabled'
    })
  ],
  exports: [
    RouterModule
  ]
})

My node express server is configured like this:

const http = require('http')
const express = require('express')
const app = express()
const expressStaticGzip = require('express-static-gzip')
const server = new http.Server(app)

app.use('/', expressStaticGzip(__dirname + '/dist', {
  enableBrotli: true
}))
app.use('/', express.static(__dirname + '/dist'))

require('zone.js/dist/zone-node')
require('reflect-metadata')

const ngExpressEngine = require('@nguniversal/express-engine').ngExpressEngine
const { enableProdMode } = require('@angular/core')

enableProdMode()

const {
  ServerAppModuleNgFactory,
  LAZY_MODULE_MAP
} = require('./dist-server/main.bundle')

const {
  provideModuleMap
} = require('@nguniversal/module-map-ngfactory-loader')

const provider = provideModuleMap(LAZY_MODULE_MAP)

app.engine(
  'html',
  ngExpressEngine({
    bootstrap: ServerAppModuleNgFactory,
    providers: [provider]
  })
)

app.set('view engine', 'html')
app.set('views', 'dist')

app.get('*', (req, res) => {
    res.render('index', {req, res})
})

server.listen(process.env.PORT || 7070, function () {
  console.log('listening on', server.address().port)
})

When I access ‘https://mywebsite.com‘ for the first time, my Universal app should respond with the Home Page rendered, but it’s always returning my original index.html file. But if I try to access ‘https://mywebsite.com/contact‘, I receive the contact page pre-rendered.

I can’t figure out why this is happening. Shouldn’t the root url be the easiest/trivial one to be pre-rendered?

Google and other web crawlers aren’t able to index my site if the content served by my root url (the only one known by Google and other crawlers) is my original Angular app index.html, without any content.

this is my package.json dependencies:

"dependencies": {
    "@angular/animations": "5.2.11",
    "@angular/common": "5.2.11",
    "@angular/compiler": "5.2.11",
    "@angular/core": "5.2.11",
    "@angular/forms": "5.2.11",
    "@angular/http": "5.2.11",
    "@angular/platform-browser": "5.2.11",
    "@angular/platform-browser-dynamic": "5.2.11",
    "@angular/platform-server": "^5.2.11",
    "@angular/router": "5.2.11",
    "@fortawesome/angular-fontawesome": "0.1.0-9",
    "@fortawesome/fontawesome-svg-core": "^1.2.0-11",
    "@fortawesome/free-solid-svg-icons": "^5.1.0-8",
    "@ngrx/effects": "^5.2.0",
    "@ngrx/store": "^5.2.0",
    "@ngrx/store-devtools": "^5.2.0",
    "@nguniversal/express-engine": "^5.0.0",
    "@nguniversal/module-map-ngfactory-loader": "^5.0.0",
    "@types/socket.io-client": "^1.4.32",
    "aws-sdk": "^2.250.1",
    "bcrypt": "^1.0.3",
    "body-parser": "^1.15.2",
    "compression": "^1.7.2",
    "cookie-parser": "*",
    "core-js": "^2.4.1",
    "cors": "^2.7.1",
    "express": "^4.14.0",
    "express-joi-validation": "^0.2.1",
    "express-rate-limit": "^2.11.0",
    "express-static-gzip": "^0.3.2",
    "helmet": "3.11.0",
    "heroku-ssl-redirect": "0.0.4",
    "http2": "^3.3.7",
    "image-compressor.js": "^1.1.3",
    "joi": "^13.1.2",
    "jsonwebtoken": "^7.1.9",
    "lodash": "^4.17.10",
    "mongodb": "^2.2.31",
    "mongoose": "^4.12.0",
    "morgan": "^1.7.0",
    "ngrx-store-localstorage": "^5.0.0",
    "openssl": "^1.1.0",
    "reflect-metadata": "^0.1.12",
    "rxjs": "^5.5.6",
    "zone.js": "^0.8.19"
  },
  "devDependencies": {
    "@angular/cli": "^1.7.2",
    "@angular/compiler-cli": "5.2.11",
    "@angular/language-service": "5.2.11",
    "@ngui/map": "^0.20.2",
    "@types/googlemaps": "^3.30.7",
    "@types/jasmine": "~2.8.3",
    "@types/jasminewd2": "~2.0.2",
    "@types/node": "~6.0.60",
    "autoprefixer": "^7.2.3",
    "brotli": "^1.3.2",
    "circular-dependency-plugin": "^4.2.1",
    "codelyzer": "^4.0.1",
    "compression-webpack-plugin": "^1.1.7",
    "copy-webpack-plugin": "~4.4.1",
    "cssnano": "^3.10.0",
    "file-loader": "^1.1.5",
    "html-webpack-plugin": "^2.29.0",
    "intersection-observer": "^0.5.0",
    "istanbul-instrumenter-loader": "^3.0.0",
    "jasmine-core": "~2.8.0",
    "jasmine-spec-reporter": "~4.2.1",
    "karma": "~2.0.0",
    "karma-chrome-launcher": "~2.2.0",
    "karma-coverage-istanbul-reporter": "^1.2.1",
    "karma-jasmine": "~1.1.0",
    "karma-jasmine-html-reporter": "^0.2.2",
    "less-loader": "^4.0.5",
    "nodemon": "^1.17.5",
    "postcss-import": "^11.0.0",
    "postcss-loader": "^2.0.10",
    "postcss-url": "^7.1.2",
    "protractor": "~5.1.2",
    "raw-loader": "^0.5.1",
    "sass-loader": "^6.0.6",
    "source-map-loader": "^0.2.0",
    "style-loader": "^0.19.1",
    "stylus-loader": "^3.0.1",
    "ts-node": "~3.2.0",
    "tslint": "~5.9.1",
    "typescript": "~2.5.3",
    "uglifyjs-webpack-plugin": "^1.1.8",
    "url-loader": "^0.6.2"
  },
  "engines": {
    "node": "8.9.4"
  }

And this is my angular-cli.json:

{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "project": {
    "name": "ng-test",
    "ejected": false
  },
  "apps": [
    {
      "root": "src",
      "outDir": "dist",
      "assets": [
        "assets",
        "favicon.ico"
      ],
      "index": "index.html",
      "main": "main.ts",
      "polyfills": "polyfills.ts",
      "test": "test.ts",
      "tsconfig": "tsconfig.browser.app.json",
      "testTsconfig": "tsconfig.spec.json",
      "prefix": "app",
      "styles": ["styles.scss"],
      "stylePreprocessorOptions": {
        "includePaths": [
          "./themes"
        ]
      },
      "scripts": [],
      "environmentSource": "environments/environment.ts",
      "environments": {
        "dev": "environments/environment.ts",
        "prod": "environments/environment.prod.ts"
      }
    },
    {
      "platform": "server",
      "root": "src",
      "outDir": "dist-server",
      "assets": [
        "assets",
        "favicon.ico"
      ],
      "index": "index.html",
      "main": "server.main.ts",
      "test": "test.ts",
      "tsconfig": "tsconfig.server.app.json",
      "testTsconfig": "tsconfig.spec.json",
      "prefix": "app",
      "styles": [
        "styles.scss"
      ],
      "stylePreprocessorOptions": {
        "includePaths": [
          "./themes"
        ]
      },
      "scripts": [],
      "environmentSource": "environments/environment.ts",
      "environments": {
        "dev": "environments/environment.ts",
        "prod": "environments/environment.prod.ts"
      }
    }
  ],
  "e2e": {
    "protractor": {
      "config": "./protractor.conf.js"
    }
  },
  "lint": [
    {
      "project": "src/tsconfig.app.json",
      "exclude": "**/node_modules/**"
    },
    {
      "project": "src/tsconfig.spec.json",
      "exclude": "**/node_modules/**"
    },
    {
      "project": "e2e/tsconfig.e2e.json",
      "exclude": "**/node_modules/**"
    }
  ],
  "test": {
    "karma": {
      "config": "./karma.conf.js"
    }
  },
  "defaults": {
    "styleExt": "scss",
    "component": {
    }
  }
}

Solution

Update your outDir and baseHref like:

 "apps": [
        {
          "root": "src",
          "outDir": "dist/browser",                
          "baseHref": "/",
          "assets": [
            "assets",
            "favicon.ico"
          ],

          ...
        },
        {
          "platform": "server",
          "root": "src",      
          "outDir": "dist/server",      
          "baseHref": "/",
          "assets": [
            "assets",
            "favicon.ico"
          ],
          "index": "index.html",      
          "main": "main.server.ts",     

          ...
        }
      ],

Also some changes required in your server.ts

// These are important and needed before anything else
import 'zone.js/dist/zone-node';
import 'reflect-metadata';

import { renderModuleFactory } from '@angular/platform-server';
import { enableProdMode } from '@angular/core';

import * as express from 'express';
import { join } from 'path';
import { readFileSync } from 'fs';

// Faster server renders w/ Prod mode (dev mode never needed)
enableProdMode();

// Express server
const app = express();

const PORT = process.env.PORT || 4000;
const DIST_FOLDER = join(process.cwd(), 'dist');

// Our index.html we'll use as our template
const template = readFileSync(join(DIST_FOLDER, 'browser', 'index.html')).toString();

// * NOTE :: leave this as require() since this file is built Dynamically from webpack
const { AppServerModuleNgFactory, LAZY_MODULE_MAP } = require('./dist/server/main.bundle');

const { provideModuleMap } = require('@nguniversal/module-map-ngfactory-loader');

app.engine('html', (_, options, callback) => {
  renderModuleFactory(AppServerModuleNgFactory, {
    // Our index.html
    document: template,
    url: options.req.url,
    // DI so that we can get lazy-loading to work differently (since we need it to just instantly render it)
    extraProviders: [
      provideModuleMap(LAZY_MODULE_MAP)
    ]
  }).then(html => {
    callback(null, html);
  });
});

app.set('view engine', 'html');
app.set('views', join(DIST_FOLDER, 'browser'));

// Server static files from /browser
app.get('*.*', express.static(join(DIST_FOLDER, 'browser')));

// All regular routes use the Universal engine
app.get('*', (req, res) => {
  res.render(join(DIST_FOLDER, 'browser', 'index.html'), { req });
});

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

Answered By – eduPeeth

Answer Checked By – Cary Denson (AngularFixing Admin)

Leave a Reply

Your email address will not be published.