I have checked several tutorials and walkthroughs on how to add SSR (server-side rendering) to existing angular CLI & Express projects.
I was able to run node with SSR but I'm unable to make API calls.
I wish to run one node process that can serve static file (Angular with SSR) and dynamic content with API calls which access SQLITE DB using KNEX.
This is my server.js file
// These are important and needed before anything else
import 'zone.js/dist/zone-node';
import 'reflect-metadata';
import { enableProdMode } from '#angular/core';
import * as express from 'express';
import { join } from 'path';
// Faster server renders w/ Prod mode (dev mode never needed)
enableProdMode();
// Express server
const app = express();
const PORT = process.env.PORT || 3000;
const DIST_FOLDER = join(process.cwd(), 'bin/dist');
// * NOTE :: leave this as require() since this file is built Dynamically from webpack
const { AppServerModuleNgFactory, LAZY_MODULE_MAP } = require('./bin/dist/server/main');
// Express Engine
import { ngExpressEngine } from '#nguniversal/express-engine';
// Import module map for lazy loading
import { provideModuleMap } from '#nguniversal/module-map-ngfactory-loader';
app.engine('html', ngExpressEngine({
bootstrap: AppServerModuleNgFactory,
providers: [
provideModuleMap(LAZY_MODULE_MAP)
]
}));
app.set('view engine', 'html');
app.set('views', join(DIST_FOLDER, ''));
// import * as apiS from './server/index';
// apiS.load(app);
// TODO: implement data requests securely
// app.get('/api/*', (req, res) => {
// res.status(404).send('data requests are not supported');
// });
import * as LoadRoutes from './server/boot/routes';
LoadRoutes.load(app);
// Server static files from /browser
app.get('*.*', express.static(join(DIST_FOLDER, '')));
// All regular routes use the Universal engine
app.get('*', (req, res) => {
res.render('index', { req });
});
// Start up the Node server
app.listen(PORT, () => {
console.log(`Node server listening on http://localhost:${PORT}`);
});
Is it even possible to accomplish SSR + API calls from the same node process and if so can someone post here answer?
It's already there. Just uncomment these lines and extend the API routes with Express router:
app.get('/api/*', (req, res) => {
res.status(404).send('data requests are not supported');
});
Use the router file instead:
app.use('/api/*', appRoutes);
Every URL starting with api/ will be served by backend APIs.
Related
I have implemented angular universal in my project for performance and SEO reasons! every thing works fine when I 'run ng serve' , the app can communicate with backend , but when I run 'npm run build:ssr 'and then 'ng run serve:ssr' it gives me the 404 (Not Found) for all API calls and does not render the entire app.
I would appreciate it if someone could help me .
thanks.
this is my server.ts file
import 'zone.js/dist/zone-node';
import { ngExpressEngine } from '#nguniversal/express-engine';
import * as express from 'express';
import { join } from 'path';
import { AppServerModule } from './src/main.server';
import { APP_BASE_HREF } from '#angular/common';
import { existsSync } from 'fs';
// 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-universal/browser');
const indexHtml = existsSync(join(distFolder, 'index.original.html')) ? 'index.original.html' : 'index';
// Our Universal express-engine (found # https://github.com/angular/universal/tree/master/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';
If server.ts contains api
As you have mentioned here you are running npm run build:ssr and afterwards ng run serve:ssr. But about the backend, in your server.ts file there are no routes exposed for api calls in this server. So it is obvious that the result is 404. If you are trying to expose apis from the express server then you would need to define api routes. But in your given code the portion for api calls are commented.
// Example Express Rest API endpoints
// server.get('/api/**', (req, res) => { });
// Serve static files from /browser
I had work in similar projects. While running under localhost first you would need to expose the api and make sure that calls are being made on the right port.
If different backend for api
If you have a different server for api calls (not the given server.ts) then running ng serve would only run the angular portion of your app. So both ends (that is the angular app and server for api calls) are running separately. In this case upon running npm run build:ssr and npm run serve:ssr you are running angular-universal along with the express server. They are both running on port 4000 as your server.ts file indicates hence angular is looking for apis on the express app where there are no apis exposed and hence resulting in 404. In this case running angular-universal and the api backend on a different port is an easy solution.
Are you trying to use this server.ts (angular universal server) as backend? Or you have different server for api calls. Further clarification is needed
Hi I am getting always error in production when I try to deploy to webserver.
I am deploying everything to /var/myapp folder so basically in my server I have this paths:
/var/myapp/client/dist/client/browser folder where there is index.html and
/var/myapp/client/dist/client/server where there is the main.js file
I am running on server node /var/myapp/client/dist/client/server/main.js
Please help with the right configuration on server.ts file
import { ngExpressEngine } from '#nguniversal/express-engine';
import * as express from 'express';
import { join } from 'path';
import { AppServerModule } from './src/main.server';
import { APP_BASE_HREF } from '#angular/common';
import { existsSync } from 'fs';
import 'localstorage-polyfill';
global.localStorage;
// The Express app is exported so that it can be used by serverless Functions.
export function app() {
const server = express();
const distFolder = join(process.cwd(), '/var/myapp/client/dist/client/browser');
const indexHtml = existsSync(join(distFolder, 'index.html')) ? 'index.html' : 'index';
// Our Universal express-engine (found # https://github.com/angular/universal/tree/master/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() {
const port = 4000;
// Start up the Node server
const server = app();
server.listen(port, '127.0.0.1', function() {
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';
Angular universal deployment issue content is displayed but no css.
I am getting those errors:
runtime-es2015.3f64214655012374b6e0.js:1 Failed to load module script: The server responded with a non-JavaScript MIME type of "text/html". Strict MIME type checking is enforced for module scripts per HTML spec.
polyfills-es2015.629687f397387c194377.js:1 Failed to load module script: The server responded with a non-JavaScript MIME type of "text/html". Strict MIME type checking is enforced for module scripts per HTML spec.
main-es2015.a772fe1bf7606c343730.js:1 Failed to load module script: The server responded with a non-JavaScript MIME type of "text/html". Strict MIME type checking is enforced for module scripts per HTML spec.
I was trying to use gzip to reduce the size of the bundle of my current angular project. The bundle report claims that after using gzip it would reduce my product-build's size to about 30% or less. But I don't know how to make that work.
Currently, my project is built in Angular and is sent to a port through Node.js Express.js server. Guess we would need config both Angular and NodeJS to make it work?
What I have tried so far:
It looks like the gzip can be applied in Angular 5- until Google disabled the ng eject. Also, I found a lot of Nginx tutorials with gzip but none of Node.js by now. So guess I don't quite understand either of them.
I tried to add the following code in my node server, but it doesn't seem to affect the performance of the project with or without the compression.
const express = require('express');
const http = require('http');
const https = require('https');
const compression = require('compression');
const app = express();
const port = 4201;
app.use(compression());
app.use(express.static(__dirname + '/dist'));
Edit:
After testing with http://www.gidnetwork.com/tools/gzip-test.php, my site has been compressed into gzip. But the performance in Google Page Insight doesn't change. Perhaps only the index.html is compressed?
I use node version 16.13.2 and it has a built-in compression package.
So, all you need is to use the below server.ts file as your node server
import 'zone.js/dist/zone-node';
import { ngExpressEngine } from '#nguniversal/express-engine';
import * as express from 'express';
import { join } from 'path';
import { AppServerModule } from './src/main.server';
import { APP_BASE_HREF } from '#angular/common';
import { existsSync } from 'fs';
const compression = require('compression');
// 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/your-project-name/browser');
const indexHtml = existsSync(join(distFolder, 'index.original.html')) ? 'index.original.html' : 'index';
server.use(compression());
// Our Universal express-engine (found # https://github.com/angular/universal/tree/master/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';
Try zlib, nodejs has a stable support in core now.
An example (taken from the docs):
// server example
// Running a gzip operation on every request is quite expensive.
// It would be much more efficient to cache the compressed buffer.
var zlib = require('zlib');
var http = require('http');
var fs = require('fs');
http.createServer(function(request, response) {
var raw = fs.createReadStream('index.html');
var acceptEncoding = request.headers['accept-encoding'];
if (!acceptEncoding) {
acceptEncoding = '';
}
// Note: this is not a conformant accept-encoding parser.
// See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3
if (acceptEncoding.match(/\bdeflate\b/)) {
response.writeHead(200, { 'content-encoding': 'deflate' });
raw.pipe(zlib.createDeflate()).pipe(response);
} else if (acceptEncoding.match(/\bgzip\b/)) {
response.writeHead(200, { 'content-encoding': 'gzip' });
raw.pipe(zlib.createGzip()).pipe(response);
} else {
response.writeHead(200, {});
raw.pipe(response);
}
}).listen(1337);
for angular you have to install gzip-all
npm i gzip-all
and on your package.json file
package.json
"scripts": {
"build": "ng build --prod --no-delete-output-path --aot --base-href /app/ && gzip-all \Project file.....\" ",
},
I am using angular 6 and implemented the angular universal for server side page rendering,
Everything works fine but I am getting continues error in my console.
ERROR [Error]
ERROR [Error]
And I have declared window as global variable still I am getting errors like this
Unhandled Promise rejection: window.some_function is not a
function
my server.ts file
//
import 'zone.js/dist/zone-node';
import 'reflect-metadata';
import { enableProdMode } from '#angular/core';
import * as express from 'express';
import { join } from 'path';
// Express Engine
import { ngExpressEngine } from '#nguniversal/express-engine';
// Import module map for lazy loading
import { provideModuleMap } from '#nguniversal/module-map-ngfactory-loader';
// Faster server renders w/ Prod mode (dev mode never needed)
enableProdMode();
// node run port
const port = 6065;
// Express server
const app = express();
const PORT = port || process.env.PORT;
const DIST_FOLDER = join(process.cwd(), 'dist');
const domino = require('domino');
const fs = require('fs');
const MockBrowser = require('mock-browser').mocks.MockBrowser;
const mock = new MockBrowser();
const template = fs
.readFileSync(join(DIST_FOLDER, 'browser', 'index.html'))
.toString();
// Make all Domino types available as types in the global env.
Object.assign(global, domino.impl);
(global as any)['KeyboardEvent'] = domino.impl.Event;
const win = domino.createWindow(template);
win.Object = Object;
win.Math = Math;
global['window'] = win;
global['document'] = win.document;
global['navigator'] = mock.getNavigator();
global['branch'] = null;
global['object'] = win.object;
global['HTMLElement'] = win.HTMLElement;
global['DOMTokenList'] = win.DOMTokenList;
global['Node'] = win.Node;
global['Text'] = win.Text;
// * NOTE :: leave this as require() since this file is built Dynamically from webpack
const { AppServerModuleNgFactory, LAZY_MODULE_MAP } = require('./dist/server/main');
app.engine('html', ngExpressEngine({
bootstrap: AppServerModuleNgFactory,
providers: [
provideModuleMap(LAZY_MODULE_MAP)
]
}));
app.set('view engine', 'html');
app.set('views', join(DIST_FOLDER, 'browser'));
// TODO: implement data requests securely
app.get('/api/*', (req, res) => {
res.status(404).send('data requests are not supported');
});
// 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('index', { req });
});
// Start up the Node server
app.listen(PORT, '0.0.0.0', () => {
console.log(`Node server listening on http://localhost:${PORT}`);
});
I had the same problem and started debugging server.js in my Visual Studio Code and what I had found: an error messages was caused by http requests from TranslateHttpLoader (I'm using ngx-translate), which was trying to load .json-files with translations. After some googling I found this thread where you can find how to enable server-side translation loading. After that error messages disappeared.
Goal: Modify Open Graph meta tags when web crawlers visit different routes.
I know Angular2 4.0.0 has a MetaService, and there is always jQuery, but web crawlers except Googlebot don't execute Javascript, so it is kind of useless to my purpose. While Angular Universal sounds like an overkill for changing a couple meta tags.
So far my solution is to copy and paste the compiled index.html in /dist to index.ejs, and modify the necessary fields. Is it possible to integrate the workflow with the angular-cli compilation process, by having the entry point changed from index.html to index.ejs? If not, what are the alternatives that I should explore?
In my index.ejs :
<meta property="og:url" content="<%= url %>" />
In my Express route index.js :
res.render('index', {
url: site_url,
});
In my server.js:
app.set('views', path.join(__dirname, '/dist'));
app.set('view engine', 'ejs');
Please refrain your answer to the current #angular/cli version (v1.0.1 compatible).
Some related discussions:
Add customization options for HtmlWebpackPlugin #3338
Conditional template logic in index.html for beta.11-webpack #1544
Provide config to rename index.html in dist folder #2241
I was able to solve this by using Nunjucks to render templates served via Angular Universal. I'm sure it's possible to use other engines such as EJS as well. Here are the relevant portions of my server.ts:
import * as dotenv from 'dotenv';
dotenv.config();
import 'zone.js/dist/zone-node';
import 'reflect-metadata';
import * as nunjucks from 'nunjucks';
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 || 4201;
const DIST_FOLDER = join(process.cwd(), 'dist');
// Our index.html we'll use as our template
const template = readFileSync(join(DIST_FOLDER, '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');
nunjucks.configure(DIST_FOLDER, {
autoescape: true,
express: app,
});
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');
// Server static files from dist folder
app.get('*.*', express.static(DIST_FOLDER));
// All regular routes use the Universal engine
// You can pass in server-side values here to have Nunjucks render them
app.get('*', (req, res) => {
res.render('index', { req, yourVar: 'some-value' });
});
// Start up the Node server
app.listen(PORT, () => {
console.log(`Node server listening on http://localhost:${PORT}`);
});