DevGang
Авторизоваться

SEO-Friendly Angular SPA: универсальное учебное пособие по рендерингу на стороне сервера 

Angular - это JS-фреймворк с открытым исходным кодом, разработанный инженерами Google в 2010 году.

Одностраничные приложения JS добавляют контент на страницы динамически с помощью JavaScript. Это не оптимально для SEO: роботы, скорее всего, не будут запускать код JS, таким образом, не сталкиваясь с фактическим содержанием страницы.

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

«Времена изменились. Сегодня, пока вы не запрещаете роботу Googlebot сканировать ваши файлы JavaScript или CSS, мы обычно можем отображать и понимать ваши веб-страницы, как современные браузеры».

«Иногда при рендеринге дела идут плохо, что может негативно повлиять на результаты поиска по вашему сайту».

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

В то время как Google изо всех сил пытается представить ваш JS, он все равно побеждает всех других поисковых и социальных роботов (Facebook, Twitter, LinkedIn, Yandex) Оптимизация рендеринга вашего приложения, таким образом, принесет пользу вашей деятельности на этих каналах!

Источник изображения

Это слишком много неиндексированного контента JavaScript, чтобы оставить его на столе.

Как справиться с SEO в Angular

Чтобы ваш сайт на Angular находился среди результатов поиска, вам нужно будет поработать.

Проверить как видят Google боты ваши страницы

Если у вас уже есть общедоступное приложение Angular, перейдите в консоль поиска Google и запустите Fetch as Google на страницах, которые необходимо проиндексировать. Он расскажет вам, что могут или не могут получить доступ Google-боты.

Это даст вам представление о том, какие области нуждаются в SEO-работе.

Предварительный рендеринг

Это довольно просто. JavaScript отображается в браузере; Статический HTML сохраняется и возвращается сканерам. Это решение отлично подходит для простых приложений, которые не зависят от какого-либо сервера. Это проще в настройке, чем рендеринг на стороне сервера, тем не менее, с отличными результатами SEO.

Для визуализации Angular, я предлагаю посмотреть на Prerender.io.

Рендеринг на стороне сервера

Вот что я сделаю здесь.

Я буду использовать SSR, используя Angular Universal.

Проще говоря, он будет запускать Angular на бэкэнде, так что при выполнении запроса контент будет отображаться в DOM для пользователя.

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

Техническое руководство: Angular SEO-ориентированный пример SPA с Universal

Предварительные условия

  • Базовое понимание одностраничных приложений (SPA)
  • Базовые знания Typescript [необязательно]
  • Аккаунт в Snipcart (навсегда бесплатно в тестовом режиме)

Настройка среды разработки

Установите Angular CLI глобально, используя следующую команду:

npm install -g @angular/cli

Я использую sass в своем проекте. Если вы решите сделать это и у вас он еще не установлен, то выполните следующую команду:

npm install -g sass

1. Настройка структуры проекта с использованием Angular CLI

Создайте свой проект с помощью Angular CLI.

ng new my-app --style=scss --routing

Заметили, как я добавил style и routing в команду?

Таким образом, routing и scss будут добавлены непосредственно в ваш проект, что гораздо проще, чем пытаться добавить его позже.

Как только это будет сделано, перейдите в каталог проекта и запустите свой проект.

cd my-app
ng serve

Теперь проект должен быть виден локально по адресу: http://localhost:4200/

2. Создание первого Angular компонента

Как и многие современные веб-библиотеки или платформы, Angular использует систему компонентов.

В Angular каждый компонент, кроме основного приложения, представляет собой каталог, в src/app/ содержащий три файла: файл TypeScript, файл стилей и файл HTML.

Поскольку это демо - простой интернет-магазин, я буду использовать два компонента. Компонент products будет содержать список, и ссылку на каждой странице продукта. И компонент product будет отображать всю подробную информацию о продукте.

Используйте встроенную команду Angular CLI для генерации новых компонентов:

ng generate component products
ng generate component product

2.1 Пересмотр списка товаров

Перед редактированием компонентов необходимо создать структуру данных для продуктов.

Создайте файл product.ts прямо в папке src/app/ и присвойте ему все необходимые свойства.

export class Product{
   id: string;
   name: string;
   price: number;
   weight: number;
   description: string;
}

Также создайте mocked-product.ts в том же месте. Этот файл импортирует класс Product и экспортирует массив Product.

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

import { Product } from './product';export const PRODUCTS: Product[] = [
   {
       id: "ac-1",
       name: "Central Air Conditioner",
       price: 5000.00,
       weight: 900000,
       description: "Keep the whole office cool with this Central Air Conditioner."
   },
   {
       id: "ac-2",
       name: "Window Air Conditioner",
       price: 300.00,
       weight: 175000,
       description: "Perfect to keep a room or small apartment cool."
   },
   {
       id: "ac-3",
       name: "A fan",
       price: 10.00,
       weight: 2000,
       description: "An inexpensive, but effective way to stop your coworkers from complaining about the heat."
   },
] 

3. Перечисление продуктов приложения

Теперь давайте перечислим наши продукты на главной странице. Для этого откройте products.component.ts и добавьте:

import { Component, OnInit } from '@angular/core';
import { PRODUCTS } from '../mocked-products';

@Component({
  selector: 'app-products',
  templateUrl: './products.component.html',
  styleUrls: ['./products.component.scss']
})
export class ProductsComponent implements OnInit {
  products = PRODUCTS; constructor() { } ngOnInit() { }
}

Как видите, каждый компонент Angular импортирует класс Component из базовой библиотеки Angular. @Component({}) является функцией декоратора, которая помечает класс как Angular компонент и предоставляет большую часть его метаданных.

Ключ selector - это тег XML, который можно включить в шаблоны для визуализации этого компонента.

Сделайте это сейчас, удалив все сгенерированные в шаблоне основного приложения ( app.component.html) и добавив соответствующий тег:

Как только это будет сделано, если вы посетите веб-сайт, у вас должно отобразиться:

Теперь давайте изменим файл products.component.html для перечисления продуктов, используя директиву цикла Angular *ngFor и синтаксис ( {{ }}) для привязки данных из вашего класса к шаблону.

Products

  • {{product.name}}

4. Добавление маршрутизации в приложение Angular

Давайте превратим этот магазин в одностраничное приложение, используя встроенную маршрутизацию Angular.

Поскольку вы добавили аргумент --routing при создании проекта, вы можете перейти непосредственно к файлу app-routing.module.ts и изменить его следующем образом:

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { ProductComponent } from './product/product.component';
import { ProductsComponent } from './products/products.component';

const routes: Routes = [
  {path: '', component: ProductsComponent},
  {path: 'product/:id', component: ProductComponent}
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

Этот модуль импортирует компоненты products и product, и создает массив маршрутов, связывающих каждый путь с компонентом.

Вы можете видеть, что я добавил заполнитель :id, который можно будет найти в компоненте product для отображения нужного продукта.

Также важно инициализировать маршрутизатор, добавив в него импорт модуля RouterModule.forRoot(route).

Как только это будет сделано, вы можете заменить тег компонента в шаблоне приложения (app.component.html) на тег :

Тег router-outlet отобразит компоненту для соответствующего пути, указанного в массиве routes. В этом случае корневой каталог будет отображать представление для компонента Products.

Теперь вы можете добавить routerLink='relativePath' вместо атрибута href='path' в тегах a. Например, вы можете обновить файл products.component.html примерно так:

Products

Таким образом, каждый элемент в нашем списке отправит пользователя к представлению с  компонентом product.

5. Создание компонента product

Теперь давайте создадим компонент сведений о продукте. В своем файле TypeScript product.component.ts добавьте следующий код:

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Location } from '@angular/common';

import { PRODUCTS } from '../mocked-products';
import { Product } from '../product';

@Component({
  selector: 'app-product',
  templateUrl: './product.component.html',
  styleUrls: ['./product.component.scss']
})
export class ProductComponent implements OnInit {
  products = PRODUCTS;
  url: String;
  product: Product; 
  
  constructor(
    private route: ActivatedRoute, 
    private location: Location
  ) { } 
  
  ngOnInit() {
    const id = this.route.snapshot.paramMap.get('id');
    this.product = this.findProductById(id);
    this.url = `https://snipcart-angular-universal.herokuapp.com/${this.location.path()}`;
  } 
  
  findProductById(productId: string): Product {
    return this.products.find(product => product.id === productId);
  }
} 

Выше я импортировал ActivatedRoute и Location из Angular. Это позволит вам получить из URL-адреса productId и текущий путь непосредственно в конструкторе.

Мы также импортировали наш массив продуктов, извлекли текущий продукт с использованием идентификатора из маршрута, используя функцию find().

Теперь давайте обновим шаблон компонента ( product.component.html) для отображения необходимой информации и создадим кнопку покупки, совместимую с определением продукта Snipcart.

{{product.name}}

{{product.name}}

{{product.description}}

Заметили, как я не использовал фигурные скобки для привязки данных в атрибутах HTML?

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

6. Интеграция функций корзины покупок

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

Таким образом, он сможет взаимодействовать со всем вашим приложением.




  
    
    Angular Snipcart
     
    
  

  
    
  
  
  
  
  

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

Я оставлю эту часть на ваше усмотрение, поскольку она не является основной целью руководства.

7. Использование Angular Universal для SEO

Наконец, давайте сделаем наше приложение дружественным к SEO, используя SSR.

В этой демонстрации я буду использовать сервер Node.js Express . Имейте в виду, что можно добавить Angular Universal на любой сервер, если он может взаимодействовать с функцией renderModuleFactory Angular, но конфигурация, скорее всего, будет отличаться от той, которая показана в этом посте.

7.1 Установка Angular Universal

Для начала давайте добавим необходимые инструменты в вашу среду разработки:

npm install --save @angular/platform-server @nguniversal/module-map-ngfactory-loader ts-loader @nguniversal/express-engine

7.2 Редактирование клиентского приложения

Теперь, когда у нас есть все необходимые инструменты, давайте отредактируем код на стороне клиента, чтобы разрешить переход между отображаемой страницей на стороне сервера и приложением на стороне клиента. В app.module.ts  заменить импорт BrowserModule в декораторе @NgModule() со следующим кодом:

BrowserModule.withServerTransition({ appId: 'my-app' }),

Поскольку приложение встроено в код на стороне сервера и код на стороне клиента, вам потребуется два пути вывода. Давайте начнем с определения пути вывода для браузера. Для этого отредактируйте outputPath в angular.json.

"architect": {
 "build": {
   "builder": "@angular-devkit/build-angular:browser",
   "options": {
     "outputPath": "dist/browser",
...

7.3 Настройка сервера

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

Создать app.server.module.ts в каталоге src/app.

import { NgModule } from '@angular/core';
import { ServerModule } from '@angular/platform-server';
import { ModuleMapLoaderModule } from '@nguniversal/module-map-ngfactory-loader';
import { AppModule } from './app.module';
import { AppComponent } from './app.component';

@NgModule({
  imports: [
    AppModule,
    ServerModule,
    ModuleMapLoaderModule
  ],
  bootstrap: [ AppComponent ],
})
export class AppServerModule {}

Создайте файл main.server.ts в каталоге src/, который экспортирует AppServerModule, который будет действовать как точка входа для вашего сервера.

export { AppServerModule } from './app/app.server.module';

Также создайте файл server.ts в корневом каталоге вашего приложения. Этот файл содержит код сервера Express. Он будет слушать входящий запрос, обслуживать требуемый ресурс и визуализировать HTML - страницу с помощью вызова renderModuleFactory (обернуто в ngExpressEngine).

import 'zone.js/dist/zone-node';
import 'reflect-metadata';

import { enableProdMode } from '@angular/core';
import * as express from 'express';
import { join } from 'path';

// Более быстрый рендеринг сервера с режимом Prod (режим разработки никогда не нужен)
enableProdMode();

// Express server
const app = express();
const PORT = process.env.PORT || 4000;
const DIST_FOLDER = join(process.cwd(), 'dist');

// оставьте это как require(), так как этот файл создается динамически из веб-пакета
const { AppServerModuleNgFactory, LAZY_MODULE_MAP } = require('./dist/server/main');

// Express Engine
import { ngExpressEngine } from '@nguniversal/express-engine';

// Импортируем карту модуля для отложенной загрузки
import { provideModuleMap } from '@nguniversal/module-map-ngfactory-loader';

app.engine('html', ngExpressEngine({
  bootstrap: AppServerModuleNgFactory,
  providers: [
    provideModuleMap(LAZY_MODULE_MAP)
  ]
}));

app.set('view engine', 'html');
app.set('views', join(DIST_FOLDER, 'browser'));

// TODO: безопасно выполнять запросы данных
app.get('/api/*', (req, res) => {
  res.status(404).send('data requests are not supported');
});

// Статические файлы сервера из /browser
app.get('*.*', express.static(join(DIST_FOLDER, 'browser')));

// Все маршруты используют Universal engine
app.get('*', (req, res) => {
  res.render('index', { req });
});

// Запустить Node-сервер
app.listen(PORT, () => {
  console.log(`Node server listening on http://localhost:${PORT}`);
});

Теперь, когда вы настроили сервер, вам нужно будет добавить и обновить файлы конфигурации. Создать файл tsconfig.server.json в каталоге src.

{
  "extends": "../tsconfig.json",
  "compilerOptions": {
    "outDir": "../out-tsc/app",
    "baseUrl": "./",
    "module": "commonjs",
    "types": []
  },
  "exclude": [
    "test.ts",
    "**/*.spec.ts"
  ],
  "angularCompilerOptions": {
    "entryModule": "app/app.server.module#AppServerModule"
  }
}

Настройте файл webpack.server.config.js в корневом каталоге приложения с помощью следующего кода:

const path = require('path');
const webpack = require('webpack');

module.exports = {
  entry: { server: './server.ts' },
  resolve: { extensions: ['.js', '.ts'] },
  target: 'node',
  mode: 'none',
  
  // это гарантирует, что мы включим node_modules и другие сторонние библиотеки
  externals: [/node_modules/],
  output: {
    path: path.join(__dirname, 'dist'),
    filename: '[name].js'
  },
  module: {
    rules: [{ test: /\.ts$/, loader: 'ts-loader' }]
  },
  
  plugins: [
    // Временное решение проблемы: https://github.com/angular/angular/issues/11580
    // для 'WARNING Critical dependency: the request of a dependency is an expression'
    new webpack.ContextReplacementPlugin(
      /(.+)?angular(\\|\/)core(.+)?/,
      path.join(__dirname, 'src'), // местоположение вашего источника
      {} // карта ваших маршрутов
    ),
    new webpack.ContextReplacementPlugin(
      /(.+)?express(\\|\/)(.+)?/,
      path.join(__dirname, 'src'),
      {}
    )
  ]
};

Теперь обновите конфигурацию Angular CLI и установите выходной путь сборки сервера, добавив следующий код в angular.json:

"architect": {
 ...
 "server": {
   "builder": "@angular-devkit/build-angular:server",
   "options": {
     "outputPath": "dist/server",
     "main": "src/main.server.ts",
     "tsConfig": "src/tsconfig.server.json"
   }
 }
}

Наконец, добавьте команды build и serve в раздел сценариев package.json.

Таким образом, вы сможете сохранить нормальный рендеринг ng serve на стороне клиента и использовать для рендеринга на стороне сервера с Universal npm run build:ssr && npm run serve:ssr.

"scripts": {
   ...
   "build:ssr": "npm run build:client-and-server-bundles && npm run webpack:server",
   "serve:ssr": "node dist/server",
   "build:client-and-server-bundles": "ng build --prod && ng run my-app:server",
   "webpack:server": "webpack --config webpack.server.config.js --progress --colors"
}

7.4 Сборка и запуск приложения

Теперь, когда все настроено, вам нужно его запустить! Создайте приложение и запустите сервер.

npm run build:ssr && npm run serve:ssr

GitHub репозиторий и live demo

Смотрите репозиторий GitHub здесь

Смотрите демо здесь

Заключительные мысли

В общем, создание этой демонстрации с Angular было приятным опытом. Создать проект и понять общую идею и концепции Angular было проще, чем я думал - это был мой первый раз с этой платформой! Я могу определенно увидеть, как этот подход может быть полезным для многих проектов.

Однако включить Universal в мой проект оказалось сложнее, чем я ожидал; это действительно легко потеряться во всех файлах конфигурации!

Мне понадобилось чуть меньше двух дней, чтобы создать эту демонстрацию, включая первоначальное чтение и исправление ошибок. Документация Angular была полной и легкой для понимания, поскольку каждый фрагмент кода был тщательно объяснен.

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

Надеюсь, что это поможет вам правильно настроить SEO в Angular! :)

Перевод статьи: SEO-Friendly Angular SPA: Universal Server-Side Rendering Tutorial

#Angular
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться

Присоединяйся в тусовку

Поделитесь своим опытом, расскажите о новом инструменте, библиотеке или фреймворке. Для этого не обязательно становится постоянным автором.

Попробовать

Оплатив хостинг 25$ в подарок вы получите 100$ на счет

Получить