I have different routes in an angular web app, like '/about', '/contact' etc.
After running build:ssr, I run ng run myproject:prerender
This completes and creates folders in /dist/myproject/browser/*. A different folder for each route.
In each folder is an index.html file which is the rendered route HTML. All good so far.
However, in each of the index.html rendered files, the first line looks like this:
<html lang="en"><head><script src="runtime-es2015.c9afb3256f2870e161de.js" type="module"></script><script src="runtime-es5.c9afb3256f2870e161de.js" nomodule="" defer=""></script><script src="polyfills-es5.2878b91c9d9c0a1ee890.js" nomodule="" defer=""></script><script src="polyfills-es2015.4cfd8e3e2fd9d10b9e9e.js" type="module"></script><script src="vendor-es2015.429669b901f4df42a057.js" type="module"></script><script src="vendor-es5.429669b901f4df42a057.js" nomodule="" defer=""></script><script src="main-es2015.a676e1a4bb3cc6e8a80c.js" type="module"></script><script src="main-es5.a676e1a4bb3cc6e8a80c.js" nomodule="" defer=""></script>
This looks fine until you realise that the script src is relative.
So when deployed, lets say https://myproject.com/about would load the rendered index.html from the /about/ folder. However, when loading scripts, because the src is relative, the script paths become: https://myproject.com/about/runtim-es2015....js.
This returns a 404 and crashes the app because the scripts don't exist in that folder. Those scripts are located in the root directory.
Does that make sense?
I created a temporary fix by writing and including the following script in my deployment pipeline, which adds a forward slash to the src paths:
const { readdirSync } = require('fs')
const fs = require('fs')
const getDirectories = source =>
readdirSync(source, { withFileTypes: true })
.filter(dirent => dirent.isDirectory())
.map(dirent => dirent.name)
const loop = function (DirectoryString) {
const Dirs = getDirectories(DirectoryString);
if (Dirs.length) {
Dirs.forEach(folderName => {
const DirectoryStr = './dist/myproject/browser/' + folderName + '/';
if (fs.existsSync(DirectoryStr)) {
console.log(DirectoryStr + 'index.html Fixed');
loop(DirectoryStr);
fs.readdir(DirectoryStr, (err, files) => {
files.forEach(file => {
if (file === "index.html") {
const fileName = `${DirectoryStr}index.html`;
try {
fs.readFile(fileName, 'utf8', function (err, data) {
if (err) {
return console.log(err);
} else {
var result = data.replace(/script src\=\"(?!http)([a-zA-Z])/g, 'script src="/$1');
fs.writeFile(fileName, result, 'utf8', function (err) {
if (err) return console.log(err);
});
}
});
} catch (e) {
console.log(e);
}
}
});
});
}
});
}
}
loop('./dist/myproject/browser/');
This works, for now, however, I would like a way to fix this in Angular. I couldn't find anyone else with this same problem. Any suggestions?
UPDATE
The issue also happens sometimes in localhost development. It happens if the route is more than one path deep, so to speak.
So the ng server does an automatic reload of localhost:4200/order/step2 (triggered from updating the code), the app doesn't load and I get the error
GET http://localhost:4200/order/step2/runtime.js net::ERR_ABORTED 404 (Not Found)
This is quite annoying for development. The project never used to this, only started recently :(
UPDATE 2
Also, I observe when viewing the source of the localhost rendered page, that the script tags are added outside the document.
From the top, this is the rendered source of the document:
<script src="runtime.js" type="module"></script><script src="polyfills.js" type="module"></script><script src="styles.js" type="module"></script><script src="vendor.js" type="module"></script><script src="main.js" type="module"></script><!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
Why on earth would Angular do this?
UPDATE 3
I have <base href="/"> in my document head, but I suspect that because the script tags are being added before the base declaration, and outside the document, that the base is not being applied.
UPDATE 4
Here is a github repo that reproduces the issue:
"deployUrl": "/",in theoptionssection of theprerendertarget ofangular.jsonfileSchema validation failed with the following errors: Data path "" should NOT have additional properties(deployUrl).deployUrlsetting, you need to add it to theproductionconfiguration of thebuildtarget of angular.json file. I tried this and it adds/to the script path in index.html when I runng run project:prerender:productionYou can do the same for other configurations if it should apply to other configurations. However I'm not sure why your scripts are added to the top of the page...index.htmlfiles in your dist folder, do they also have the scripts on top of the file before thebasetag? Dumb question, do you have some code that creates/moves dom elements (appendChild/renderer/...) that could be buggy?