Summary: Long question so here's the quick summary. I can run the commands ng build && ng run app-name:server and npm run dev:server successfully and see no errors on console whenever I try to connect to server. But page is loading infinitely. I have tried to apply NgxSsrTimeoutModule.forRoot({ timeout: 500 }) nothing changed. Made other changes but saw no progress. Really stuck at this point having headaches.
Hello, I'm trying to convert a SPA Angular application to an SSR-compatible one. Application is a little bit chunky, so I couldn't create a new SSR rendered application and move the modules to that one, one by one. First, my versions are:
@angular-devkit/architect 0.1301.2
@angular-devkit/build-angular 13.1.2
@angular-devkit/core 13.1.2
@angular-devkit/schematics 13.1.2
@angular/cli 13.1.2
@angular/platform-server 13.1.3
@nguniversal/builders 13.1.1
@nguniversal/express-engine 13.0.2
@schematics/angular 13.1.2
rxjs 6.6.7
typescript 4.5.4
I have made some changes to get development server working. First change was on angular.json. I had error Angular Universal Configuration 'development' is not set in the workspace:
"serve-ssr": {
"builder": "@nguniversal/builders:ssr-dev-server",
"configurations": {
"production": {
"browserTarget": "appName:build:production",
"serverTarget": "appName:server:production"
},
"development": ...
},
"defaultConfiguration": "development"
},
- First change was on angular.json. Referring to this issue
I have changed as this:
"serve-ssr": {
"builder": "@nguniversal/builders:ssr-dev-server",
"options": { "browserTarget": "appName:build", "serverTarget": "appName:server"},
"configurations": {
"production": {
"browserTarget": "appName:build:production",
"serverTarget": "appName:server:production"
}
}
},
- Second change was on ServerModule.
We had an application starter basically an Angular service requesting an appConfig.json to make application configurations. I had Could not load file '../../assets/config/appConfig.json' which is an error thrown from that service. So basic ServerModule changed as this;
import { NgModule, Inject } from '@angular/core';
import { INITIAL_CONFIG, PlatformConfig, ServerModule } from '@angular/platform-server';
import { AppModule } from './app.module';
import { AppComponent } from './app.component';
import { NgxSsrTimeoutModule } from '@ngx-ssr/timeout';
@NgModule({
imports: [
AppModule,
ServerModule,
NgxSsrTimeoutModule.forRoot({ timeout: 500 })
],
bootstrap: [AppComponent],
})
export class AppServerModule {
constructor(@Inject(INITIAL_CONFIG) private config: PlatformConfig) {
this.config.useAbsoluteUrl = true;
}
}
- Also solved the "window not defined" error by adding
dominolibrary to the server code.
After changing this, I could successfully run the comman npm run dev:ssr. Take note that, before doing this, I could run the command ng build && ng run app-name:server but not npm run dev:ssr. And after that, I had a running Node server on my localhost:4200 but page is always in loading state, I could see nothing on network tab (Chrome Dev Tools), no client side application taking action and nothing on console (just says Messages received, value changed...). After all these, I implemented *appShellRender and *appShellNoRender directives, and just decided to render a loading animation in the server, nothing changed;
app.component.ts:
<div *appShellRender>
<app-loading-animation></app-loading-animation>
</div>
<div *appShellNoRender>
<ng-container appCardQuery appSystemIdTracker>
<mat-sidenav-container>
<mat-sidenav #sidenav>
<app-side-nav (closeSideNav)="sidenav.close()"></app-side-nav>
</mat-sidenav>
<mat-sidenav-content (scroll) = "onScroll($event)">
<ng-container *ngIf="isToolbarOn">
<app-toolbar (SideNavToggle)="sidenav.toggle()"></app-toolbar>
</ng-container>
<main>
<!-- <div style="background-color: black; width: 250px;height: 250px;" (click)="testClick()"></div> -->
<router-outlet></router-outlet>
</main>
<div class="spacer30"></div>
<ng-container *ngIf="isAppAboutOn">
<app-about></app-about>
</ng-container>
</mat-sidenav-content>
</mat-sidenav-container>
</ng-container>
</div>
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';
const domino = require('domino');
// 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/appName/browser');
const indexHtml = existsSync(join(distFolder, 'index.original.html')) ? 'index.original.html' : 'index';
const win = domino.createWindow(indexHtml);
global['window'] = win;
global['document'] = win.document;
global['navigator'] = win.navigator;
global['screen'] = win.screen;
// 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';
Error Possibilities I see no errors on console but there's obviously something wrong. We're using AgmCoreModule which is an Angular component to use Maps on application. Also leaflet.
index.js:
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A==" crossorigin="" />
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js" integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA==" crossorigin=""></script>
These might be causing errors?