Since Angular Universal isn’t going to be in the CLI for quite a while I must use prerender.io to allow SEO to work properly. However, after some tests it appears that it doesn’t work all that great because it doesn’t appear to wait for lazy loaded modules so the SEO still fails.
On their site at this location, they say this:
Is your page only partially rendered?
Our Prerender server tries its best to determine when the page is done
loading by counting the number of requests in flight. Once the number
of requests in flight reaches zero, we wait a short period of time and
then save the HTML. If this is saving the page too early, you can use
our window.prerenderReady flag to notify the server that your page is
ready to be saved.Put this in your HTML:
<script> window.prerenderReady = false; </script>
When we see window.prerenderReady set to false, we’ll wait until it’s
set to true to save the HTML. Execute this when your page will be
ready (usually after ajax calls):window.prerenderReady = true;
Here’s where I don’t understand how to go about it. Since Angular removes script tags from the template (not sure if this is just CLI behavior or Angular itself), I can’t set the initial script tag. Nor can I figure out how I would update the window
property prerenderReady
inside a template script tag from my component class, since I can’t just use window.prerenderReady = true
and expect it to work since the script has to be added to the template.
Has anyone managed to figure this one out or have any ideas on how I could achieve this?
3
Answers
REMOVE:
server.use(prerender.removeScriptTags());
prerender.io can be set to leave in scripts so they can run.
My Meteor-Angular2 setup gave me the same grief. My header and footer modules would render, but not my router-outlet output. I placed:
<script> window.prerenderReady = false; </script>
on my main index.html (containing the main
<app></app>
tag), and placed:window.prerenderReady = true;
at the end of my last callback. Then I commented out:
//server.use(prerender.removeScriptTags());
in the server.js of my prerender server – and it finally rendered correctly.
Okay, I got this working, but it seems like a rather complex solution, so hopefully there’s someone out there smarter than me at this 🙂
First, uncomment all the browser polyfills in
src/polyfills.ts
(it may be that not all of them are needed, but I haven’t tested them one-by-one):Next, you need to get your
prerenderReady
flag in a script tag in the<head>
of yourindex.html
. Angular doesn’t strip out comments so before I build I leave a flag in there:Now, AFTER you run
ng build
, you need to replace that flag with the actual script tag. I do this using a shell script and thesed
command. Something like:NOTE
I have a vague recollection of installing
sed
on OSX, but I don’t really remember the steps. If you’re not on linux andsed
doesn’t work for you, you’ll just have to Google around for a solution.Finally, you need to set that
prerenderReady
flag to true somewhere. I do this in theAppComponent
‘sOnInit
implementation:It probably also bears mentioning that in order to use
window
in a TypeScript app you need something like this in file I calltypings.d.ts
:As well as a reference to that typings file I put at the top of my
app.module.ts
file:Hope that helps someone out.
I would suggest putting the first script in index.html near the top of the page before Angular JS files are injected using angular.json or edit webpack build to inject:
window.prerenderReady = false
Then in the main app.ts file, get a reference to the window object and put window.prerenderReady = true in the ngOnViewInit hook: