The first call to our SPA preferred URL: domain.com/route
will 301 redirect to domain.com/route/
with a trailing slash. When hydrated, it would rewrite location back to the original, without registering any network redirects. According to a very old article on Google Search Central, it has no effect on searchability. Nevertheless, it made a lot of people nervous since the search console displays the following when trying to crawl prerendered URLs.
Let’s address this issue and try to fix it.
anchorPrerendering
There are multiple ways to generate pre-rendered pages, one of them is through the built-in Angular builder,
ng run prerender
Another is through Express. Read about how to Prerender Angular in Express in a previous article.
All of the normal ways create index.html
pages with the content, inside folders reflecting the route:
domain/{route}/index.html
That in the world of hosting means if user browses to domain/route
it automatically redirects to domain/route/
and displays the index file. That redirect is a 301.
Following are solutions to this problem
anchorAngular solution: out of the box
Here is how Angular defines the server.ts
to take care of this problem:
// server.ts
server.get('*.*', express.static(distFolder, {
maxAge: '1y'
}));
// All regular routes use the Universal CommonEngine
server.get('*', (req, res) => {
res.render(indexHtml, { req, providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }] });
});
If you pay close attention, this has a server.get(*.*)
for all static files (JavaScript and CSS). The pre-rendered pages are served by the second rule.
In ngExpressEngine
, the CommonEngine
checks if the page path already exists, it serves the file, else rolls back to SSR generated main.js
. This is not always an option.
// CommonEngine code
async render(opts: RenderOptions): Promise<string> {
// ...
if (opts.publicPath && opts.documentFilePath && opts.url !== undefined) {
// ...
if (pageExists) {
// Serve pre-rendered page.
return fs.promises.readFile(pagePath, 'utf-8');
}
}
}
}
One instance that won't work is cloud hosting that prefers static files first. Like Firebase or Netlify.
Note: in order to test the behavior, keep the Network tab open, and turn off JavaScript
as well. You will spot the 301 right at the top.
anchorFirebase solution: trailingSlash
What happens is that it looks for the physical file, before it serves the ssr
serverless function (or the general rewrite to index file if you are not using SSR). The default behavior is to add a trailing slash when it finds the index.html
.
Firebase hosting configuration: trailingSlash
can take care of this issue.
// firebase.json
{
"hosting": {
"public": "functions/public",
// Removes trailing slashes from URLs
"trailingSlash": false
// ...
}
}
This displays /route/index.html
as /route
, without a 301. But it would redirect /route/
to /route
with a 301. If you never use /route/
anywhere, not in your sitemaps, nor inline links, nor when you share on social media, you should be fine. If however it still bugs you, there is another solution to put this to rest, that solution works well in Express servers as well. We will discuss this solution in the next episode.
anchorNetlify solution, or lack thereof
Physical file preference will kick in first, before it runs the netlify.toml
hosting file. The default is to add the /
if it finds an index.html
. According to their documentation there is a way to unify all links to have a trailing slash. In an Angular app, this is not what we are looking for. It rather is the other way around.
There is another default behavior of Netlify: to look for /route.html
if user browses to /route
. And that is what we want. In fact, that is the only way it's done on Netlify. The solution then is to rewrite our prerender function to generate /route.html
instead of /route/index.html
. This also will be covered in the next episode along with Express server solution.
Stay tuned for the next episode coming soon.