A website I am working on is using Angular Universal 10.1.1 and is hosted on an IIS Server. An example of this happening is https://tragoa.com/welcome
If I navigate to the page from the root like foo.com then the website goes straight to the correct route foo.com/welcome without a 301 redirect. However, if I attempt to load foo.com/welcome directly then it will return that request with a 301 redirect to foo.com/welcome/ and that redirect request is returned with a 200 OK and then the trailing slash is stripped in the url bar. So it goes:
The main issue here is the unwanted redirect
This only occurs when the page is using the prerendered HTML. It does not redirect if there is no prerendered index HTML for the given route.
This has caused issues with Lighthouse and some 3rd party redirect issues.
Does anyone know what could be causing this?
Some information that may help:
The base href of the html is <base href="/"> NOTE: I change this to not have the / then the routes get doubled up like foo.com/welcome/welcome
My server.ts get is this:
import 'zone.js/dist/zone-node';
import { ngExpressEngine } from '@nguniversal/express-engine';
import * as express from 'express';
import { join } from 'path';
import { existsSync, readFileSync } from 'fs';
import 'localstorage-polyfill';
const domino = require('domino');
let distFolder = join(process.cwd(), '../spa/browser');
const template = readFileSync(join(distFolder, 'index.html')).toString();
const win = domino.createWindow(template);
win.Object = Object;
win.Math = Math;
global['window'] = win;
global['document'] = win.document;
global['branch'] = null;
global['object'] = win.object;
global['HTMLElement'] = win.HTMLElement;
global['navigator'] = win.navigator;
global['localStorage'] = localStorage;
import { AppServerModule } from './src/main.server';
import { APP_BASE_HREF } from '@angular/common';
export function app(): express.Express {
const server = express();
const indexHtml = existsSync(join(distFolder, 'index.original.html')) ? 'index.original.html' : 'index.html';
server.engine('html', ngExpressEngine({
bootstrap: AppServerModule,
}));
server.set('view engine', 'html');
server.set('views', distFolder);
server.get('*.*', express.static(distFolder, {
maxAge: '1y'
}));
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;
const server = app();
server.listen(port, () => {
console.log(`Node Express server listening on http://localhost:${port}`);
});
}
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';
I use this command to create my site and then deploy the files "prerender": "ng build --configuration production && ng run my-app.spa:prerender:production"
I finally figured it out
Short answer: Because of the way angular universal pre-rendering works you can't remove "/" at the end for the pre-rendered pages, Your only solution is to make all your routes end with "/" by default, so when angular starts and is redirecting it will also redirect to the exact same url hence not counting as a redirect
Long answer:
Why is this happening?
First we need to understand why is this happening in the first place. So the way angular pre-rendering works is that for each route a directory/folder by the name of that route is generated with an "index.html" file inside. So lets say if I have a route "mywebsite.net/page1", angular universal will generate a directory "page1" with an index.html file inside containing the html code of that page. In both nginx and apache servers a directory is represented by a "/" relative to the base directory. So if the server want to access index.html file inside of that page1 folder the path would be represented as "/page1/", notice the trailing slash in the end meaning we are inside the page1 directory, alternatively if we had a "page1.html" file at the root directory that would be represented as "/page1" but that's not how angular universal generates it's pages, as mentioned above it generates directories with index.html files inside.
Why is there a 301 redirect
So when we first visit any route let's say "/page1", our server goes inside the page1 directory and opens up index.html file inside, and because it is a directory and not a file in the root directory it adds "/" in the end and once that index.html file is rendered the angular script runs it redirect you to the route you have defined in your angular routes, if it is without a "/" it will redirect to it, hence removing the slash. This is why slash is added and then removed. In your question you mentioned that chrome remove the "/" slash but that is not correct, angular does
Solution
The solution is to make all your routes end with a trailing slash by default. In you every module you will define your routes as
const routes: Routes = [
{ path: 'page1/.', component: page1Component, data: { title: "page q", subTitle: " " } },
...
];
notice "/." at then end of the path and in your href
<a href="" routerLink="/page1/.">Page1</a>
and in your main.ts you can add some logic to add a trailing slash to the links without one. something like this
import { Location } from '@angular/common';
const __stripTrailingSlash = (Location as any).stripTrailingSlash;
(Location as any).stripTrailingSlash = function _stripTrailingSlash(url: string): string {
const queryString$ = url.match(/([^?]*)?(.*)/);
if (queryString$[2].length > 0) {
return /[^\/]\/$/.test(queryString$[1]) ? queryString$[1] + '.' + queryString$[2] : __stripTrailingSlash(url);
}
return /[^\/]\/$/.test(url) ? url + '.' : __stripTrailingSlash(url);
};
Doing all this fixes the redirect issue.
apologies for my bad english, I hope you can understand my explanation
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With