Angular + Node + Universal remove # with working refresh - node.js

I am using Angular 13 + Angular Universal + Node.js.
I have deployed app on server, but I have big problem with removing # from URL.
I have built app using
npm run build:ssr
And I am serving it using node.js app on my server using :
app.use(express.static(__dirname + '/dist/app-name/browser'));
app.get('/', (req, res) => res.sendFile(path.join(__dirname)));
When I use HashLocationStrategy, then everything works fine. Problem appears when I am trying to remove hash from link.
I changed it to PathLocationStrategy and I am getting error 404 on refresh on other path than '/'.
I was trying to add different routes in node.js file to sendFile (f.e.)
app.get('/subpage', (req, res) => res.sendFile(path.join(__dirname)));
But it didn't worked for me.
I was also trying to overwite # with node.js, but as it comes from angular app it's impossible to do it from node.js code.
My files :
app.server.module.ts
import { NgModule } from '#angular/core';
import { ServerModule } from '#angular/platform-server';
import { AppModule } from './app.module';
import { AppComponent } from './app.component';
import {UniversalInterceptor} from './universal-interceptor.service';
import {HTTP_INTERCEPTORS} from '#angular/common/http';
import {NgxSsrTimeoutModule} from '#ngx-ssr/timeout';
#NgModule({
imports: [
AppModule,
ServerModule,
NgxSsrTimeoutModule.forRoot({ timeout: 5000 }),
],
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: UniversalInterceptor,
multi: true,
}
],
bootstrap: [AppComponent],
})
export class AppServerModule {}
server.ts
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/app-name/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) => {
res.status(404).send('data requests are not yet supported');
});
// 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';
How to to deploy app without '#' in url with working refresh?

Okay, so I finally found solution for this. My problem was, that I needed to give absolute path to my index.html file for all angular routes. So finally i changed
app.get('/', (req, res) => res.sendFile(path.join(__dirname)));
to
app.get('/**', (req, res) => {
res.sendFile(path.join(__dirname + '/dist/app-name/browser/index.html'))
});

Try this:
app.get('*', (req, res) => res.sendFile(path.join(__dirname)));
put it near the end middleware chain

Related

How to do an app logout using nodejs and okta

I am currently using Okta with OIDC and Node Express to log into a web app that I made. I want my users to be able to logout of just my app and not okta itself as the okta is for my company and that would log them out of all the other websites that use the company okta. Okta recommends doing this on their website.
app.get('/local-logout', (req, res) => {
req.logout();
res.redirect('/');
});
(https://developer.okta.com/docs/guides/sign-users-out/nodeexpress/sign-out-of-your-app/)
I have tried implementing this but it doesn't work. I have also tried using req.session.destroy, res.clearCookie("connect.sid"), req.session = null and many different combinations of these with or without callbacks. Whenever I redirect, it just goes back to the homepage and the same user is logged in. Whenever I try deleting the connect.sid cookie it deletes and then is reinitialized when the user is redirected. I'm not really sure what to do. My code with irrelevant endpoints removed is below.
require('dotenv').config();
import 'zone.js/dist/zone-node';
import { ngExpressEngine } from '#nguniversal/express-engine';
import { join } from 'path';
import * as express from 'express';
import { AppServerModule } from './src/main.server';
import { APP_BASE_HREF } from '#angular/common';
import { existsSync, read } from 'fs';
// 30 minutes
const sessionMaxAge = 1800000;
import * as e from 'cors';
import { resolveForwardRef } from '#angular/core';
import { User } from '#okta/okta-sdk-nodejs';
const session = require('express-session');
const { ExpressOIDC } = require('#okta/oidc-middleware');
const cookieParser = require("cookie-parser");
/** Creates the OpenID Connect Middleware and configures it to work with Okta */
const oidc = new ExpressOIDC({
appBaseUrl: process.env.HOST_URL,
issuer: `${process.env.OKTA_ORG_URL}/oauth2/default`,
client_id: process.env.OKTA_CLIENT_ID,
client_secret: process.env.OKTA_CLIENT_SECRET,
redirect_uri: process.env.REDIRECT_URL,
scope: 'openid profile',
routes: {
loginCallback: {
path: '/authorization-code/callback'
},
}
});
// The Express app is exported so that it can be used by serverless Functions.
export function app(): express.Express {
var root_folder: string = process.env.ROOT_FOLDER || "dist/angular-app/browser"
const server = express();
const distFolder = join(process.cwd(), root_folder);
const indexHtml = existsSync(join(distFolder, 'index.original.html')) ? 'index.original.html' : 'index';
// Our Universal express-engine (found # https://urldefense.proofpoint.com/v2/url?u=https-3A__github.com_angular_universal_tree_master_modules_express-2Dengine&d=DwIGAg&c=-35OiAkTchMrZOngvJPOeA&r=03NdPO1x-l0QAZ_R9TNGwA&m=WF5ia-YADjCituVWMV5vLoZ5wg7d_W1qhCYDTbJNGT0&s=WPOkeRsetPDQ6TrD26RKLo1m9_zxBfQXGhUUSkth0Ew&e= )
server.engine('html', ngExpressEngine({
bootstrap: AppServerModule,
}));
server.set('view engine', 'html');
server.set('views', distFolder);
// Serve static files from /browser
server.get('*.*', express.static(distFolder, {
maxAge: '1y'
}));
// Configure Session Support
server.use( session({
secret: process.env.APP_SECRET,
resave: false,
saveUninitialized: false,
cookie: {maxAge: sessionMaxAge}
})
);
server.use(cookieParser());
server.use(oidc.router);
// Log the user out of the local session
server.get('/local-logout',(req:any, res:any) => {
req.logout();
res.redirect('/');
});
// All regular routes use the Universal engine
server.get('*', oidc.ensureAuthenticated({ redirectTo: '/login' }), (req: any, res: any) => {
if(req.session && req.session.username != null) {
console.log(process.env);
res.render(indexHtml, { req, providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }] });
}
else {
console.log("getting user info");
getUserInfo(req)
.then(userInfo => {
req.session.username = userInfo.username;
req.session.group = userInfo.group;
if (userInfo.group=="unauthorized") {
res.sendFile('./403.html', {root: "."})
}
else{
console.log(process.env);
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 https://urldefense.proofpoint.com/v2/url?u=http-3A__localhost-3A-24-257Bport-257D&d=DwIGAg&c=-35OiAkTchMrZOngvJPOeA&r=03NdPO1x-l0QAZ_R9TNGwA&m=WF5ia-YADjCituVWMV5vLoZ5wg7d_W1qhCYDTbJNGT0&s=PUb7XMS4uP9ICqUY28QgXNRxoWk6sGatPdmZMqmgbJs&e= `);
});
}
// 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';
After an exhaustive search, I don't think it is possible. I ended up doing req.logout() and then redirecting to a page that says "you have been logged out of the app but may still be logged into your single sign on provider."

Angular Universal Server Side Rendering (SSR) res.render not loading

I am trying to add SSR to my web site but the content is not loaded. I am using Angular Universal, I followed this guide for the initial configuration. http://localhost:4200/ it is not finishing loading and no errors been shown. http://localhost:4200/index.html is returning a empty view.
The build process successfully.
server.ts
import 'zone.js/dist/zone-node';
import { ngExpressEngine } from '#nguniversal/express-engine';
import * as express from 'express';
import { join } from 'path';
const path = require('path');
const fs = require('fs');
const domino = require('domino');
const templateA = fs.readFileSync(path.join('dist/web/browser', 'index.html')).toString();
const win = domino.createWindow(templateA);
global['window'] = win;
global['document'] = win.document;
// Express server
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/web/browser');
const indexHtml = existsSync(join(distFolder, 'index.original.html')) ? 'index.original.html' : 'index.html';
// 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';
tsconfig.server.json
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "./tsconfig.app.json",
"compilerOptions": {
"outDir": "./out-tsc/server",
"target": "es2016",
"types": [
"node"
],
"module": "commonjs"
},
"files": [
"src/main.server.ts",
"server.ts"
],
"angularCompilerOptions": {
"entryModule": "./src/app/app.server.module#AppServerModule"
}
}
I found a soluciĆ³n.
I followed this steps to realize what was wrong:
Comment all imports and routings from app.module.ts, I just leve the app.component.html with a simple tag just to show something. In this step the view start loading.
Add module per module and see where stops loading.
Where stops I comment line per line to see where is the mistake.
The main mistakes that I found was the order of the routing imports on app.module.ts and sub-modules,
#NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule.withServerTransition({ appId: 'serverApp' }),
FormsModule,
ReactiveFormsModule,
BrowserAnimationsModule,
HttpClientModule,
AppRoutingModule, // <-- here
],
providers: [{ provide: HTTP_INTERCEPTORS, useClass: RequestInterceptorService, multi: true }],
bootstrap: [AppComponent]
})
export class AppModule {}
another was that in some constructor components I was doing an api call but fails because the authentication. So I need to add a validation for only do it if is a platform browser:
constructor(#Inject(PLATFORM_ID) private platformId: Object) {
if (isPlatformBrowser(this.platformId)) {
// api call
}
}
Fortunately my project it isn't very big yet.

Error: ENOENT: no such file or directory, open 'dist/index.html'

So, I've been trying for days to get a angular universal app running but I keep getting this issue when I try to run the server like
npm run dev:ssr
I have set my server.ts file as in the below link
https://github.com/Angular-RU/angular-universal-starter/blob/master/server.ts
My server.ts file is as below
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';
// ssr DOM
const domino = require('domino');
const fs = require('fs');
const path = require('path');
// index from browser build!
const template = fs.readFileSync(path.join('.', 'dist', 'index.html')).toString();
// for mock global window by domino
const win = domino.createWindow(template);
// from server build
const files = fs.readdirSync(`${process.cwd()}/dist-server`);
// mock
global['window'] = win;
// not implemented property and functions
Object.defineProperty(win.document.body.style, 'transform', {
value: () => {
return {
enumerable: true,
configurable: true,
};
},
});
// mock documnet
global['document'] = win.document;
// othres mock
global['CSS'] = null;
// global['XMLHttpRequest'] = require('xmlhttprequest').XMLHttpRequest;
global['Prism'] = null;
// 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(), 'dist');
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() {
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';
my app.server.module.ts file:
import { NgModule } from '#angular/core';
import { ServerModule } from '#angular/platform-server';
import { AppModule } from './app.module';
import { AppComponent } from './app.component';
#NgModule({
imports: [
AppModule,
ServerModule,
],
bootstrap: [AppComponent],
})
export class AppServerModule {}
I am using Angular 9 and at a stage where I am thinking of dropping the idea of using angular universal at all. Seems it its way too unstable to be used at the moment.
Contents of dist folder:
Does anyone here have a solution to this?
You incorrectly set the distFolder variable. The distFolder from server.ts must point to the files containing the client side app, which is dist\YourProjectName\browser in your app. Note that this configured in angular.json file.
To correct your error, try changing the distFolder path in your server.ts
const distFolder = join(process.cwd(), 'dist','YourProjectName', 'browser');

React app with typescript I want to integrate SSR in my app But got error

I'm using ReactDOMServer to render an app, and I got the following error:
I've setup express in react typescript project. What am I doing wrong? Please help me out, thanks you.
Here's the relevant code:
App.tsx
import * as React from 'react';
class App extends React.Component {
render() {
return (
<h1>Testing App Page</h1>
);
}
}
export default App
entry.js
require('ignore-styles');
require("#babel/register")({
ignore: [ /(node_modules)/ ],
presets: [[
"#babel/preset-env",
{
"targets": {
"esmodules": true
}
}
], '#babel/preset-react']
});
require('./index');
index.js
import express from 'express';
import serverRenderer from './middleware/renderer';
const PORT = 3000;
const path = require('path');
const app = express();
const router = express.Router();
router.use('^/$', serverRenderer);
// other static resources should just be served as they are
router.use(express.static(
path.resolve(__dirname, '..', 'build'),
{ maxAge: '1d' },
));
app.use(router);
app.listen(PORT, (error) => {
console.log("App port listen on ", PORT);
});
middleware/renderer.js
import React from 'react'
import ReactDOMServer from 'react-dom/server'
import App from '../../src/App';
// const App = require("../../src/App"); <= also try this but not work
const path = require("path");
const fs = require("fs");
export default (req, res, next) => {
// point to the html file created by CRA's build tool
const filePath = path.resolve(__dirname, '..', '..', 'build', 'index.html');
fs.readFile(filePath, 'utf8', (err, htmlData) => {
if (err) {
console.error('err', err);
return res.status(404).end()
}
// render the app as a string
const html = ReactDOMServer.renderToString(React.createElement(<App />));
// inject the rendered app into our html and send it
return res.send(
htmlData.replace(
'<div id="root"></div>',
`<div id="root">${html}</div>`
)
);
});
}
Your problem is not related to typescript. In middleware/renderer.js you wrote:
const html = ReactDOMServer.renderToString(React.createElement(<App />));
This is incorrect. Change it to:
const html = ReactDOMServer.renderToString(React.createElement(App));
And you're good to go.
You should read the error message. It's already told you what is going wrong. Also learn more about the React concepts, especially the difference between "JSX Element" and "JSX Component".

Angular Universal page loading/refreshing issue

I have added Angular Universal in my project(upgraded to angular 8). Which is comparatively a big project. After solving all the window, jquery and localstorage issue I have successfully able to run the app. But the problem is, the server runs on localhost:4000. when I type localhost:4000 in browser nothing happens. But if I put localhost:4000/index.html, It starting to load fine and return to base localhost:4000. Then every angular route works fine. But if I try to refresh any page it doesn't load anything.
my server.ts
import 'zone.js/dist/zone-node';
// ssr DOM
const domino = require('domino');
const fs = require('fs');
const path = require('path');
const template = fs.readFileSync(path.join('dist/browser', 'index.html')).toString();
const win = domino.createWindow(template);
global['window'] = win;
Object.defineProperty(win.document.body.style, 'transform', {
value: () => {
return {
enumerable: true,
configurable: true,
};
},
});
// mock documnet
global['document'] = win.document;
// othres mock
global['CSS'] = null;
// global['XMLHttpRequest'] = require('xmlhttprequest').XMLHttpRequest;
global['Prism'] = null;
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() {
const server = express();
const distFolder = join(process.cwd(), 'dist/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
// app.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 = 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;
if (mainModule && mainModule.filename === __filename) {
run();
}
export * from './src/main.server';
I think i need some change with this file to load correctly.

Resources