SEO в Angular без рендеринга на стороне сервера — Блог о самом интересном.

От создателя: понимаете ли вы, что Гугл Search поддерживает сканирование JavaScript с мая 2019 года? Это значит, что рендеринг на стороне сервера больше не единственный метод сделать лучше SEO.

Введение: Angular и SEO

Традиционная неувязка одностраничных приложений и поисковых машин

Одностраничные приложения (SPA) имеют лишь одну главную страничку, index.html, которая получает обновления DOM при помощи JavaScript.

До недавнешнего времени у поисковых машин были трудности с пуском таковых приложений. У всех современных JavaScript-фреймворков был суровый недочет исходя из убеждений SEO.

Поисковая машина не могла найти, были ли вы на главной страничке либо читали статью, не могла получить доступ к динамическому контенту либо найти конфигурации head.

Хотя это быть может применимым для неких компаний, это представляет делему для секторов, таковых как электрическая коммерция, которые полагаются на индексацию симпатичных заглавий и описаний товаров для вербования возможных клиентов.

Решением была гибридизация. Приложения, которые отчасти загружали контент при помощи рендеринга на стороне сервера, сохраняя при всем этом некие достоинства фреймворков JavaScript.

Текущее состояние поиска Гугл

Гугл Dev Summit 2020 отдал разрабам много нужных советов. Я отыскал вдохновение для данной статьи в этом видео, где юрист разрабов Гугл Мартин Сплитт проливает свет на данную тему. В итоге:

Может ли Гугл обнаруживать конфигурации в приложениях Javascript?

Разрабам главных javascript-фреймворков, таковых как Angular, React либо Vue, не стоит волноваться. С мая 2019 года Гугл Search поддерживает Javascript.

… Они могут косвенно воздействовать на SEO через показатель кликабельности

Управление по внедрению

Интегрированные инструменты Angular: DOCUMENT, Meta & Title

Мы можем получить доступ к DOCUMENT для редактирования его Title и Meta при помощи инструментов, предоставленных в @angular/common и @angular/platform-browser.

Так как мы будем устанавливать мета и заголовок в почти всех частях приложения, давайте упростим граф зависимостей, создав ординарную службу SEO.

JavaScript import { DOCUMENT } from ‘@angular/common’; import { Inject, Injectable } from ‘@angular/core’;

import { Meta, Title } from ‘@angular/platform-browser’;

@Injectable({ providedIn: ‘root’ })

export class SEOService {

constructor( @Inject(DOCUMENT) private dom, private titleSvc: Title, private metaSvc: Meta,

) { }

updateTitle(title: string){ this.titleSvc.setTitle(title)

}

updateDescription(content: string) { this.metaSvc.updateTag({ name: ‘description’, content })

}

createCanonicalLink(url: string) { let link: HTMLLinkElement =

this.dom.createElement(‘link’);

link.setAttribute(‘rel’, ‘canonical’); link.setAttribute(‘href’, url); this.dom.head.appendChild(link);

}

}

12345678910111213141516171819202122232425262728293031323334 import { DOCUMENT } from ‘@angular/common’;import { Inject, Injectable } from ‘@angular/core’;import { Meta, Title } from ‘@angular/platform-browser’; @Injectable({  providedIn: ‘root’})export class SEOService {   constructor(    @Inject(DOCUMENT) private dom,    private titleSvc: Title,    private metaSvc: Meta,  ) { }    updateTitle(title: string){    this.titleSvc.setTitle(title)  }   updateDescription(content: string) {    this.metaSvc.updateTag({ name: ‘description’, content })  }   createCanonicalLink(url: string) {    let link: HTMLLinkElement =      this.dom.createElement(‘link’);     link.setAttribute(‘rel’, ‘canonical’);    link.setAttribute(‘href’, url);    this.dom.head.appendChild(link);  } }

Раздел уровня странички, таковой как домашняя страничка либо перечень статей, также будет иметь статический заголовок и описание.

Так как эти характеристики соединены с определенным маршрутом, мы будем употреблять Router для отправки инфы компоненту. Angular Routes дозволяет отправлять любые статические данные, используя данные характеристики.

Чтоб все было понятно, сделайте один файл для хранения всех статических метаданных вашего веб-сайта

Опосля определения некого мета статического раздела мы добавляем его в маршрутизатор.

Вот что я не знал о «content»

JavaScript const sectionRoutes: Routes = [ { path: », data: sectionsMetadata.homePage, children: [ { path: ‘articles’, data: sectionsMetadata.articles, loadChildren: () => import(‘./pages/articles/articles.module’).then(m => m.ArticlesModule), }, ] },

]

@NgModule({ declarations: [], imports: [ CommonModule, RouterModule.forRoot(sectionRoutes), ], exports: [RouterModule] })

export class AppRoutingModule { }

1234567891011121314151617181920212223 const sectionRoutes: Routes = [ {   path: »,   data: sectionsMetadata.homePage,    children: [      {        path: ‘articles’,        data: sectionsMetadata.articles,        loadChildren: () => import(‘./pages/articles/articles.module’).then(m => m.ArticlesModule),      },    ]  },] @NgModule({  declarations: [],  imports: [    CommonModule,    RouterModule.forRoot(sectionRoutes),  ],  exports: [RouterModule]})export class AppRoutingModule { }

Потом мы можем обновить мета, когда составляющие инициализируются:

JavaScript @Component({ selector: ‘app-articles’, templateUrl: ‘./articles.component.html’, styleUrls: [‘./articles.component.scss’], changeDetection: ChangeDetectionStrategy.OnPush, })

export class ArticlesComponent implements OnInit {

readonly articles$ = this.route.data.pipe(pluck(‘articles’))

constructor( private SEOService: SEOService, private route: ActivatedRoute,

) { }

ngOnInit(): void { const { meta } = this.route.snapshot.data; this.SEOService.updateTitle(meta.title); this.SEOService.updateDescription(meta.description);

}

}

12345678910111213141516171819202122 @Component({  selector: ‘app-articles’,  templateUrl: ‘./articles.component.html’,  styleUrls: [‘./articles.component.scss’],  changeDetection: ChangeDetectionStrategy.OnPush,})export class ArticlesComponent implements OnInit {   readonly articles$ = this.route.data.pipe(pluck(‘articles’))   constructor(    private SEOService: SEOService,    private route: ActivatedRoute,  ) { }   ngOnInit(): void {    const { meta } = this.route.snapshot.data;    this.SEOService.updateTitle(meta.title);    this.SEOService.updateDescription(meta.description);  } }

Но… что происходит, когда метаданные поступают из элемента снутри HttpResponse? Когда дело доходит до динамического контента, следует учесть две вещи: навигацию по характеристикам и асинхронные данные.

Когда мы перебегаем от одной статьи к иной, используя маршрут с таковыми параметрами, как articles/:id, компонент не создается повторно. Он просто обновляет и повторно показывает контент.

Это значит, что нам необходимо обрабатывать конфигурации через Observables, если мы желаем динамически обновлять метаданные.

Внедрение резолвера для подготовительной подборки асинхронных данных

Чтоб показывать правильную информацию, мы желаем, чтоб метаданные были доступны до сотворения экземпляра компонента либо окончания навигации по маршрутизатору.

Это можно создать при помощи Angular resolver. Мы делаем HTTP-запрос и ждем ответа, до этого чем запускать навигацию.

JavaScript @Injectable({ providedIn: ‘root’ })

export class ArticleResolver implements Resolve {

constructor(private articlesService: ArticlesAPIService) {}

resolve(route: ActivatedRouteSnapshot): Observable {
const id = route.paramMap.get(‘id’);

return this.articlesService.getArticle$(id); }

}

12345678910111213 @Injectable({   providedIn: ‘root’ })export class ArticleResolver implements Resolve {   constructor(private articlesService: ArticlesAPIService) {}   resolve(route: ActivatedRouteSnapshot): Observable {    const id = route.paramMap.get(‘id’);     return this.articlesService.getArticle$(id);  }}

Добавление резольвера в маршрутизатор

JavaScript import { Routes, RouterModule } from ‘@angular/router’; import { NgModule } from ‘@angular/core’;

import { CommonModule } from ‘@angular/common’;

import { ArticleResolver } from ‘./_providers/article.resolver’;
import { ArticlesComponent } from ‘./articles/articles.component’;

export const articlesRoutes: Routes = [ { path: », component: ArticlesComponent, }, { path: ‘:id’, resolve: { article: ArticleResolver }, loadChildren: () => import(‘./article/article.module’).then(m => m.ArticleModule)

}

]

@NgModule({ declarations: [ArticlesComponent], imports: [ CommonModule, RouterModule.forChild(articlesRoutes), ] })

export class ArticlesModule { }

12345678910111213141516171819202122232425262728 import { Routes, RouterModule } from ‘@angular/router’;import { NgModule } from ‘@angular/core’;import { CommonModule } from ‘@angular/common’; import { ArticleResolver } from ‘./_providers/article.resolver’;import { ArticlesComponent } from ‘./articles/articles.component’; export const articlesRoutes: Routes = [  {    path: »,    component: ArticlesComponent,  },  {    path: ‘:id’,    resolve: { article: ArticleResolver },    loadChildren: () => import(‘./article/article.module’).then(m => m.ArticleModule)  } ] @NgModule({  declarations: [ArticlesComponent],  imports: [    CommonModule,    RouterModule.forChild(articlesRoutes),  ]})export class ArticlesModule { }

Обновление компонента

В конце концов, мы можем употреблять разрешенные данные в компоненте и вызвать службу SEO для обновления метаданных и заголовков. Всякий раз, когда распознаватель получает новейший элемент, он автоматом обновляется.

JavaScript @Component({ selector: ‘app-article’, templateUrl: ‘./article.component.html’, styleUrls: [‘./article.component.css’], changeDetection: ChangeDetectionStrategy.OnPush, })

export class ArticleComponent {

readonly article$: Observable = this.route.data .pipe( pluck(‘article’), tap(article => this.updatePageMeta(article))

);

constructor( private SEOSvc: SEOService, private route: ActivatedRoute

) { }

private updatePageMeta(article: Article) { this.SEOSvc.updateTitle(article.title); this.SEOSvc.updateDescription(article.description)

}

}

1234567891011121314151617181920212223242526 @Component({  selector: ‘app-article’,  templateUrl: ‘./article.component.html’,  styleUrls: [‘./article.component.css’],  changeDetection: ChangeDetectionStrategy.OnPush,})export class ArticleComponent {   readonly article$: Observable = this.route.data    .pipe(      pluck(‘article’),      tap(article => this.updatePageMeta(article))  );   constructor(    private SEOSvc: SEOService,    private route: ActivatedRoute  ) { }   private updatePageMeta(article: Article) {    this.SEOSvc.updateTitle(article.title);    this.SEOSvc.updateDescription(article.description)  }  }

Как постоянно, спасибо за внимание.

Создатель: Fer Ayguavives

Редакция: Команда webformyself.

Источник

Вам также может понравиться