SEO или поисковая оптимизация играет важную роль в каждом веб-приложении. Несмотря на то, что Google достаточно умен, вам не нужно быть глупым. Когда дело доходит до SEO в приложении Angular, следует учитывать несколько фундаментальных моментов.
1. Полифиллы ES6
Это самая простая и, тем не менее, самая важная часть. Во-первых, не все поисковые боты умеют читать и отображать JavaScript. На самом деле, по моим наблюдениям, только Google достаточно умен для рендеринга JS-приложения. Ни Bing, ни Яндекс-боты на это не способны. И все же Googlebot не поддерживает стандарт ES6. Другими словами, если вы не закомментируете следующие строки внутри файла polyfils.ts:
import 'core-js/es6/symbol'; import 'core-js/es6/object'; import 'core-js/es6/function'; import 'core-js/es6/parse-int'; import 'core-js/es6/parse-float'; import 'core-js/es6/number'; import 'core-js/es6/math'; import 'core-js/es6/string'; import 'core-js/es6/date'; import 'core-js/es6/array'; import 'core-js/es6/regexp'; import 'core-js/es6/map'; import 'core-js/es6/weak-map'; import 'core-js/es6/set'; import 'core-js/es6/reflect';
Ваше приложение не будет отображаться роботом Googlebot и, следовательно, не будет отображаться в результатах поиска Google.
2. Заголовки страниц
Излишне говорить, что заголовки играют неотъемлемую часть SEO. На самом деле заголовок отражает содержание вашей страницы, и его нужно устанавливать с умом. Другими словами, предварительно задав заголовки, вам необходимо проанализировать поисковые запросы (Google Keyword Planner - это инструмент для анализа популярности поисковых запросов или для поиска относительных поисковых запросов или для определения уровня конкуренции для данного ключевого слова). Эта статья не связана с аспектами SEO, поэтому я не буду акцентировать внимание на этом аспекте.
Для установки заголовков Angular имеет встроенную службу заголовков. Наиболее рациональный подход к настройке заголовков для статических маршрутов - использование объекта данных маршрута.
{ path: 'contact-us', component: ContactComponent, data: { title: { text: 'Contact', } } }, { path: 'about', component: AboutComponent, data: { title: { text: 'About', } } }
Я рекомендую написать свой собственный сервис заголовков для вашего проекта. Потому что вам может потребоваться установить собственные префиксы и суффиксы для заголовков. Вы можете использовать эту услугу или изменить ее в соответствии со своими требованиями.
import { Injectable } from '@angular/core'; import { Title } from '@angular/platform-browser'; import { ActivatedRoute, Router, NavigationEnd } from '@angular/router'; import { environment } from '@environments/environment'; import { filter, map } from 'rxjs/operators'; import { CoreModule } from '@core/core.module'; interface TitleConfig { text: string; appendSuffix: boolean; customSuffix: string; } @Injectable({ providedIn: 'root' }) export class TitleService { constructor( protected activatedRoute: ActivatedRoute, protected router: Router, protected title: Title, ) { this.router.events .pipe( filter(event => event instanceof NavigationEnd), map(() => { let child = this.activatedRoute.firstChild; while (child) { if (child.firstChild) { child = child.firstChild; } else if (child.snapshot.data && child.snapshot.data['title']) { return child.snapshot.data['title']; } else { return null; } } return null; }) ) .subscribe((titleConfig: TitleConfig) => { if (titleConfig && titleConfig.text) this.setTitle( titleConfig.text, titleConfig.appendSuffix, titleConfig.customSuffix ); else this.setTitle(environment.title.default, false); }); } setTitle( title: string, appendSuffix: boolean = true, customSuffix?: string ): void { if (title) { let newTitle: string = title; if (appendSuffix === true) { if (customSuffix) newTitle += customSuffix; else if (environment.title.suffix) newTitle += environment.title.suffix; } this.title.setTitle(newTitle); } } }
Что касается динамических страниц - это страницы, содержимое которых извлекается асинхронно. Затем просто вставьте эту услугу в свой компонент и соответствующим образом установите заголовок.
export class ProductComponent implements OnInit{ constructor(private titleService: TitleService) {} ngOnInit() { // Get data asynchronously and then // Set Title .then(result => { this.titleService.setTitle(result.title); }) } }
3. Карта сайта и robot.txt
Поисковые системы очень хороши в определении ссылок, когда дело доходит до статических html-страниц, но не очень хороши для одностраничных приложений. На самом деле робот Googlebot очень хорош в получении ссылок на статические маршруты, маршруты, которые определяются вручную внутри RouterModule, то есть:
const routes: Routes = [ { path: 'first-page', component: FirstPageComponent, }, { path: 'second-page', component: SecondPageComponent, } ]; @NgModule({ imports: [ RouterModule.forRoot(routes) ], exports: [RouterModule] })
Но не очень хорошо с динамическими маршрутами, то есть:
<li *ngFor="let category of categories"> <a [routerLink]="['blog', category.id]">{{ category.name }}</a> </li>
В результате вы получите статические страницы в результатах поиска.
Файл Sitemap поможет вам решить эту проблему. Просто создайте ссылки на свои динамические страницы и загрузите этот файл в поисковую систему. Например, вы можете создать облачную функцию, которая автоматически генерирует файл карты сайта. Для firebase это можно настроить следующим образом:
{ "hosting": { ... "rewrites": [ { "source": "/sitemap", "destination": "/sitemap.xml" // Static Sitemap File }, { "source": "/sitemap-blog", "function": "sitemap-blog" // Dynamic Sitemap Function }, ... ] } }
Чтобы передать файл карты сайта в Google, вы можете:
- Разместите ссылку на карту сайта в файле robot.txt. Предпочтительный метод, потому что он применим к другим поисковым системам, а также к сканеру facebook.
- Дайте ссылку на этот файл в Google Search Console. Может использоваться вместе с robot.txt
Содержимое robot.txt может выглядеть следующим образом:
# Sitemaps Sitemap: https://my-app.com/sitemap-categories Sitemap: https://my-app.com/sitemap-products # Configs User-agent: * Crawl-delay: 10 # Pages excluded from indexing Disallow: /user/*
К счастью, вы можете предоставить несколько карт сайта, их проще создавать по отдельности на основе содержимого.
Автоматически сгенерированное содержимое карты сайта для динамических страниц может выглядеть следующим образом:
<?xml version='1.0' encoding='UTF-8'?> <urlset xmlns='http://www.sitemaps.org/schemas/sitemap/0.9'> <url><loc>https://my-app.com/blog/1</loc></url> <url><loc>https://my-app.com/blog/2</loc></url> <url><loc>https://my-app.com/blog/3</loc></url> </urlset>
4. Канонические ссылки
Каноническая ссылка - это элемент HTML, который помогает поисковым системам предотвращать проблемы с дублированием контента. Для этого он указывает «канонический URL», «предпочтительную» версию страницы.
Поисковые системы обычно сами определяют предпочтительные URL-адреса, но вы можете помочь им в этом, вручную установив канонические ссылки для всех страниц, они находятся внутри блока заголовка вашего html и обычно выглядят следующим образом:
<link rel="canonical" href="https://my-app.com/product/slug">
Я настоятельно рекомендую вам установить канонические значения, потому что, если у вас много страниц, боты поисковых систем иногда могут исключать некоторые из них из поискового индекса, предполагая дублированный контент. Это может сделать очень простая функция:
updateCanonicalLink() { const link = <HTMLLinkElement>( this.document.head.querySelector("link[rel='canonical']") ) || this.document.head.appendChild(this.document.createElement('link')); link.rel = 'canonical'; link.href = this.document.URL; }
5. Предварительный рендеринг
Хотя бот Google достаточно умен, я могу гарантировать, что он никогда не будет на 100% правильным, когда дело доходит до рендеринга одностраничного приложения. Кроме того, существуют различные поисковые системы, которые вообще не могут отображать JS-приложения, но игнорировать потенциальных посетителей среди них нерационально. И, наконец, не забывайте о социальных ботах.
Когда дело доходит до предварительного рендеринга, есть 3 решения:
- Angular Universal: рендеринг на стороне сервера
- Rendertron: безголовое решение для рендеринга в Chrome
- Услуги предварительной обработки: https://prerender.io, https://www.prerender.cloud и т. Д.
Угловой универсальный
Я не буду описывать, как это работает, вы можете просто обратиться к официальной документации. Это довольно понятно и понятно. Мне это не очень нравится.
Rendertron
Это отличное решение. Хотя есть пара моментов, которые стоит учесть.
Рендертрон состоит из 3-х частей:
- Скрипт промежуточного программного обеспечения (обязательно). Скрипт, определяющий поисковых и социальных ботов.
- Сервер рендеринга (обязательно). Это может быть физический / облачный Hetzner или Digital Ocean или AWS EC2 / Google Cloud.
- Веб-кэш (необязательно). Varnish Cache, nginx и т. Д.
По цене все зависит от размера вашего приложения. Мы рассчитали на основе следующих требований (~ 10 тыс. Страниц, скорость рендеринга 5 секунд, еженедельные обновления кеша, масштабируемость), и это выглядит следующим образом:
- Хетцнер. Примерно 50 $ в месяц
- Digital Ocean: около 80 долларов в месяц
- Google Cloud: 100 долларов в месяц
- AWS EC2 180 $ в месяц.
Вы можете игнорировать веб-кеш, но тогда ваше приложение будет очень упрямым, представьте, что каждый раз, когда GoogleBot посещает вашу страницу, ваш сервер рендеринга будет читать и отображать вашу страницу (это не только вызывает большой дополнительный трафик, но и приводит к потере ресурсов сервера хостинга).
В целом rendertron - отличное решение для приложений среднего и большого размера. Но если у вас ограниченный бюджет и у вас небольшое приложение, вам лучше рассмотреть следующее решение.
Услуги предварительной обработки
Я рекомендую вам использовать этот https://prerender.io, потому что он имеет отличную цену (до 20 000 страниц стоит всего 15 долларов в месяц), но также предоставляет встроенные решения для автоматического кэширования и промежуточное ПО для ExpressJS, Rails, Nginx, Apache и т. Д. Его очень легко настроить. И, наконец, вы можете предварительно кэшировать все свои страницы, предоставив карту сайта.
Это подход, который я использовал для настройки Firebase.
содержимое firebase.json:
{ "hosting": { "public": "dist", "ignore": ["firebase.json", "**/.*", "**/node_modules/**"], "rewrites": [ { "source": "**", "function": "prerender" } ] } }
Облачная функция:
import * as functions from 'firebase-functions'; import fetch from 'node-fetch'; import * as express from "express"; export const app = express(); app.use(require('prerender-node').set('prerenderToken', functions.config().prerender.token)); app.get('*', (req, res) => { fetch(`https://${functions.config().prerender.app_url}/${functions.config().app.index}`) .then(fetched => fetched.text()) .then(body => { res.send(body.toString()); }) .catch(err => { console.log(err); }); }); exports.prerender = functions.https.onRequest(app);
Резюме
После этих небольших усилий ваше присутствие в поиске приложения Angular и результаты будут радикально улучшены.