Reputation: 847
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": {
}
}
}
Upvotes: 4
Views: 8905
Reputation: 847
First of all, thank you very much for your answers.
My problem was that this express configuration:
app.use('/', expressStaticGzip(__dirname + '/dist/browser', {
enableBrotli: true
}))
app.use('/', express.static(__dirname + '/dist/browser'))
was conflicting with:
app.get('*', (req, res) => {
res.render(join(DIST_FOLDER, 'browser', 'index.html'), {req, res})
})
So I've removed the "app.use('/'" commands from my app and, as @eduPeeth suggested, I've added the following configurations to my server:
app.get('*.*', expressStaticGzip(join(DIST_FOLDER, 'browser'), {
enableBrotli: true
}))
app.get('*.*', express.static(join(DIST_FOLDER, 'browser')))
app.get('*', (req, res) => {
res.render(join(DIST_FOLDER, 'browser', 'index.html'), {req, res})
})
Nothing was wrong with my .angular-cli.json file, just my paths were different from the suggested ones.
Now SSR is working everywhere and I can serve gziped files.
Thanks to everybody!
Upvotes: 2
Reputation: 1868
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}`);
});
Upvotes: 3
Reputation: 11
The problem I was having last night was that I hadn't run build:ssr after I made changes.
Judging from everything you've got going on, I doubt that's your problem too, but nonetheless, I thought I would share.
Best of luck.
Upvotes: -1