When we talk about modern online stores, we imagine servers that are hard to understand, rendering thousands of static pages. Moreover, these thousands of rendered pages are one of the reasons why Single Page Applications did not take root in e-commerce. Even the largest e-commerce stores still look like a bunch of static pages. For the user, this is an endless cycle of clicks, expectations and page reloads.
Single-page applications are pleasantly distinguished by the dynamism of user interaction and the more complex UX. But sadly not usually user comfort is sacrificed for SEO optimization. For SEOs, a site on angular is a kind of problem, since it is difficult for search engines to index pages with dynamic content.
Another disadvantage of the SPA
is the preview of the site. For example, a user just bought a new TV in our online store and wants to recommend it to his friends in social networks. The preview of the link in the case of Angular
will look like this:
We love JS and Angular. We believe that a cool and convenient UX can be built on this technology stack, and we can solve all the related problems. At some point, we ran into Angular Universal
. This is the Angular
module for server-side rendering. At first it seemed to us, here it is - the solution! But the joy was premature - and the lack of large projects with its application is proof of that.
As a result, we began to develop components for the online store, using the usual Angular 2
, and waited for Universal
be combined with Angular Core
. At the moment, the merging of projects has not yet happened, and it is not yet clear when it will happen (or how the final version will be compatible with the current implementation), but Universal
itself has already migrated to the Angular
repository in github.
Despite these difficulties, our goal remained the same - to create cool web applications on Angular
with server rendering for indexing by search engines. As a result, our team has developed more than 20 universal components for the rapid assembly of an online store . How was this eventually achieved?
First of all, let's discuss what Angular Universal is . When we run our application on Angular 2
and open the source code, we’ll see something like this:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Angular 2 app</title> <!-- base url --> <base href="/"> <body> <app></app> </body> </html>
Front-end frameworks, such as Angular, dynamically add content, styles, and data to a tag.
For an application that is primarily focused on business and is dependent on the indexation of the site by search engines, it is important that our content is indexed and was in the top of Google.
To solve problems with Angular Universal
indexing, we are able to perform server-side rendering. Our page will be created on the “backend server” written on Node.Js, .NET
, and the user's browser will get a page with all the usual tags in it - , -
.
')
In our case, this is excellent, because search engine crawlers are very well prepared for indexing static web pages. We are developing a standard application on Angular
, and Universal
takes care of server rendering.
Up to this point, everything looks good? Perhaps, but the devil is hidden in the details, as always.
So, we want to share with you the pitfalls that we encountered on our way.
When we started testing the components of our store using Universal
, we had to spend some time to understand why our server crashes when it starts up without displaying the server page. For example, we have a Session Flow component that tracks user activity during a session (user movement, clicks, referrer, information about the user's device, etc.). After searching for issues on GitHub
we realized that Universal
does not have a wrapper over the DOM.
DOM
.
If you clone this Angular Universal starter and open browser.module.ts you will see that in the providers
array the Universal developers provide two boolean
:
providers: [ { provide: 'isBrowser', useValue: isBrowser }, { provide: 'isNode', useValue: isNode }, ... ]
So, if you want rendering on the server to work, you need to inject these variables into your components and wrap code fragments in the runtime check where the interaction with the DOM
takes place directly. For example, like this:
@Injectable() export class SessionFlow{ private reffererUrl : string; constructor(@Inject('isBrowser') private isBrowser){ if(isBrowser){ this.reffererUrl = document.referrer; } } }
Universal
automatically adds false
if it is a server, and true
if the browser. Maybe Universal
developers will later review this implementation, and we will not have to worry about it.
If you want to actively interact with DOM elements, use Angular
API services such as ElementRef
, Renderer
or ViewContainer
.
Since the server reflects our application, we had a problem with the routing.
, Angular, .
Therefore, do not forget to add the same routing on the server as on the client. For example, we have such routes on the client:
[ { path: '', redirectTo: '/home', pathMatch: 'full' }, { path: 'products', component: ProductsComponent }, { path: 'product/:id', component: ProductComponent} ]
Then you need to create a server.routes.ts
file with an array of server routes. You can not add the root route:
export const routes: string[] = [ 'products', 'product/:id' ];
Finally, add routes to the server:
import { routes } from './server.routes'; ... other server configuration app.get('/', ngApp); routes.forEach(route => { app.get(`/${route}`, ngApp); app.get(`/${route}/*`, ngApp); });
One of the most important features of Angular Universal
is the pre-rendering. From the Kissmetrics study, it follows that 47% of consumers expect a web page to load in 2 seconds or less. It was very important for us to display the page as quickly as possible. Thus, the pre-rendering at Universal
is just about our task. Let's take a closer look at what it is and how to use it.
When a user opens the URL of our store, Universal
immediately returns a pre-prepared HTML page with content, and then then begins to download the entire application in the background. Once the application is fully loaded, Universal
replaces the original page with our application. You ask what will happen if the user starts interacting with the page before downloading the application? Do not worry, the Preboot.js library will record all the events that the user will execute and after downloading the application will execute them already in the application.
To enable pre-rendering, simply add the preboot: true
server preboot: true
:
res.render('index', { req, res, preboot: true, baseUrl: '/', requestUrl: req.originalUrl, originUrl: `http://localhost:${ app.get('port') }` } );
In the case of an SEO site, adding meta tags is very important. For example, we have a Product component
that represents a specific product page. It should asynchronously receive product data from the database and, based on this data, add meta tags and other special information for the search robot.
The Angular Universal
team created the angular2-meta service to easily manipulate meta tags. Insert a meta-service into your component and a few lines of code add meta tags to your page:
import { Meta, MetaDefinition } from './../../angular2-meta'; @Component({ selector: 'main-page', templateUrl: './main-page.component.html', styleUrls: ['./main-page.component.scss'] }) export class MainPageComponent { constructor(private metaService: Meta){ const name: MetaDefinition = { name: 'application-name', content: 'application-content' }; metaService.addTags(name); } }
In the next version of Angular, this service will be moved to @ angular / platform-server
Angular Universal
launches your XHR request twice: one on the server and the other on loading the store application.
But why do we need to request data on the server twice? PatricJs created an example of how to make an Http request on the server once and cache the received data for the client. View the source code of the sample here . To use it in the Model service
and call the get
method to make http calls with caching:
public data; constructor(public model: ModelService) { this.universalInit(); } universalInit() { this.model.get('/data.json').subscribe(data => { this.data = data; }); }
Server-side rendering with Angular Universal
allows us to create customer-oriented e-commerce applications no longer worrying about indexing your application. In addition, the “prendering” function allows you to immediately show the site for your client, improving the rendering time (which is a rather unpleasant moment for Angular
applications due to the large size of the library itself).
Source: https://habr.com/ru/post/324926/
All Articles