SSRによるSROの研究


週間前にビルを作り始めましたSEO service それは角度のアプリのすべてのSEOのニーズをカバーしています.最後の主題は、Google検索スニペットを生成する構造化データです.
Googleの検索結果を別のスタイルであなたがそれをフィードに応じて結果を表示します.結果をフォーマットするために、GoogleはJSON - LD形式で構造化されたデータを推薦します.
この記事は構造化データの値についてではなく、追加する正しい型でもありません.構造化されたデータをサービス中に整理する方法についてです.

The final result is on StackBlitz


スニペットは難しいです!


Google Docsでのコード例のテストRich Results Testing ツール-信じないかどうか-警告を生成します.私は前にこれをして、すべての緑のチェックボックスに到達する努力の無駄です.それで、我々はちょうどためします!シンプルに保つ.

基礎


メインスクリプトは以下の通りです.
<script type="application/ld+json">
{
  "@context": "http://schema.org/",
  "@type": "type-from-gallery",
  ... props
}
</script>
それはどこでも追加することができます、我々は体の端にそれを追加します.
小道具はそれぞれのタイプに固有のものですsearch gallery . また、サブタイプを持つことができます.例えば、Recipe 型にはreview プロパティです.Review .
すべてのタイプを1つに置くことができます@graph 他のすべての型を1つのスクリプトに保持するプロパティ.

@graph is not documented on Google website, I wonder why. It is mentioned on Google Open Source Blog, and testing it on Rich Results Test tool, it works.


他のオプションはindividual item を配列に設定します.
<script type="application/ld+json">
[{
  "@context": "http://schema.org/",
  "@type": "type-from-gallery",
  ... props
},
{
  "@context": "http://schema.org/",
  "@type": "type-from-gallery",
  ... props
}]
</script>
我々が遵守する必要がある主なガイドラインは、スニペットはユーザーに表示可能なコンテンツの代表である必要があります.
まず、スクリプトを追加する必要があります@graph 配列を返します.コンストラクタで作成されたプライベートメンバーのようです.名前を挙げますsnippet の代わりにstructured data 誰も見ていないから!
export class SeoService {
  private _jsonSnippet: HTMLScriptElement;

  private createJsonSnippet(): HTMLScriptElement {
    const _script = this.doc.createElement('script');
    // set attribute to application/ld+json
    _script.setAttribute('type', 'application/ld+json');

    // append to body and return reference
    this.doc.body.appendChild(_script);
    return _script;
  }

  // add script as soon as possible
  AddTags() {
    // ... 
    // add json-ld
    this._jsonSnippet = this.createJsonSnippet();
  }
}

のボットのコンテンツとSSR


少しずつ掘り進むdocs on Google website 次のことを明らかにする.
  • Googleボットは、最初にコンテンツを読み込むためにJavaScriptを実行します.
  • ボットはhref 適切なリンク
  • スパは、それがどのようにスパであろうと、ボット(良いニュース)によって再実行されます
  • ボットはクロール前に最終的なコンテンツを待つ

  • Duplicate scripts on the same page , 問題ではない
  • これは
  • 負荷に空の配列を追加し、追加して既存の要素を更新する必要はありません.
  • ボットはとにかくページを再読み込みするが、ページのパフォーマンスについては、最初に空にしたいかもしれません.
  • 我々がSSRを実行するならば、再水和の上でスクリプトを複製することは問題でありません、しかし、それは醜いです.それで、我々は1台のプラットホームを目標とするか、既存のスクリプトをチェックします.
  • すべてのことを念頭に置いて、我々は我々のスキーマを追加を開始する準備が整いました.

    ロゴ


    正しい.一番簡単なロゴから始めましょう.最終的な結果は次のようになります.
       {
          "@type": "Organization",
          "url": "url associated with organization",
          "logo": "logo full url",
          "name": "why is google docs ignoring name?"
        }
    
    すべてのページに追加する必要はありません/ ). スニペットを更新するために、我々は書き換えますtextContent スクリプトのプロパティ.
      // SEO Service
      setHome() {
        // update snippet with logo
         const _schema = {
          "@type": "Organization",
          // url is the most basic in our case, it could be less dynamic
          // I am reusing default url, so will refactor this out later
          url: toFormat(Config.Seo.baseUrl, Config.Seo.defaultRegion, Config.Seo.defaultLanguage, ''),
          // logo must be 112px minimum, svg is acceptable
          // add this new key to config.ts
          logo: Config.Seo.logoUrl,
          // I am including name anyway
          "name": RES.SITE_NAME
        }
    
        // update script
        this.updateJsonSnippet(_schema);
      }
    
      private updateJsonSnippet(schema: any) {
        // basic, added the schema to an array
        const _graph = { '@context': 'https://schema.org', '@graph': [schema] };
        // turn into proper JSON 
        this._jsonSnippet.textContent = JSON.stringify(_graph);
      }
      // adding defaultUrl and siteUrl and refactoring service 
      get defaultUrl(): string {
        return toFormat(Config.Seo.baseUrl, Config.Seo.defaultRegion, Config.Seo.defaultLanguage, '');
      }
      get siteUrl(): string {
        return toFormat(Config.Seo.baseUrl, Config.Basic.region, Config.Basic.language, '');
      }
    
    HomeComponent
    ngOnInit(): void {
      this.seoService.setHome();
    }
    
    別の基本型に移動する

    シテリクスサーチボックス


    ルールは、1つの検索アクションを設定し、1つの文字列をクエリとして受け取ります.たとえば、レストランのアプリケーションでは、この検索URLは動作しません./search?category=chinese&price=low&open=true&nonsmoking=true&query=korma&location=sandiego&page=3単純なクエリを処理する必要があります./search?query=kormaもちろん、すべてのWebアプリケーションは、独自の目的を持って、あなたのGoogleのリストをユーザーがデフォルトで禁煙を検索できるようにする必要があります、それはあなたのニッチですので.そのような場合、スニペットで指定されたURLはプリセット条件を含んでいるべきです.
    URL自体は言語と地域情報を持つことができます.私はこれに反対する何も見つけることができませんでした、しかし、私は言語と地域を無視する例(Adobe)を見ました.それで、私はデフォルト値を使用します.
    キーワードで検索する機能を作成しますq ), ホームページに次を加えることができます.最終結果は次のようになります
       {
          "@type": "WebSite",
          "url": "https://{{default}}.domain.com/{{default}}",
          "potentialAction": {
            "@type": "SearchAction",
            "target": {
              "@type": "EntryPoint",
              "urlTemplate": "https://{{default}}.domain.com/{{default}}/projects;q={search_term}"
            },
            "query-input": "required name=search_term"
          }
        }
    
    Googleは言います:このマークアップをホームページだけに加えてください.Rightteoグーグル我々の中でsetHome :
      // ... second schema
        const _schema2 = {
          '@type': 'Website',
          url: this.defaultUrl,
          potentialAction: {
            '@type': 'SearchAction',
            target: {
              '@type': 'EntryPoint',
              urlTemplate:  this.defaultUrl + '?q={serach_term}',
            },
            'query-input': 'required name=search_term',
          },
        };
        // oh oh! need a way to append
        this.updateJsonSnippet(_schema2);
    
    私は、Aに加えるほうを選びます@graph コレクションは、簡単ですので.書き直しましょうupdate それを念頭に置いて.
      // let's keep track of the objects added
      private _graphObjects: any[] = [];
    
      private updateJsonSnippet(schema: any) {
        // first find the graph objects
        const found = this._graphObjects.findIndex(n => n['@type'] === schema['@type']);
    
        // if found replace, else create a new one
        if (found > -1) {
            this._graphObjects[found] = schema;
        } else {
            this._graphObjects.push(schema);
        }
    
        const _graph = { '@context': 'https://schema.org', '@graph': this._graphObjects };
        this._jsonSnippet.textContent = JSON.stringify(_graph);
      }
    
    それで、我々は基礎をカバーしました.どのような努力がすべての機能に必要かを見てみましょう.

    機能のスニペットを設定する


    Googleボットでのスキーマサポートはありません.最も近いものは Article . 次のような記事の断片を追加しましょう.

    Psst: Don't lose sleep over this, Google docs change, their recommendations change, and the results are never guaranteed. Stay simple, stay healthy.


      {
          "@context": "https://schema.org",
          "@type": "Article",
          "headline": "Project title",
          "image": "Project image",
          "datePublished": "date created",
          "author": [{
              "@type": "Organization",
              "name": "Sekrab Garage",
              "url": "https://www.domain.com/en/"
            }]
        }
    
    だから我々のプロジェクトではsetProject
    setProject(project: IProject) {
        // ...
        this.updateJsonSnippet({
          '@type': 'Article',
          headline: project.title,
          image: project.image,
          datePublished: project.dateCreated,
          author: [{
            '@type': 'Organization',
            name: RES.SITE_NAME,
            url: this.defaultUrl
          }]
        });
    }
    
    調査する価値があるもう一つの要素は BreadcrumbList . それはItemList . 最初の要素はプロジェクトリストへのリンクですcategory . 第2の要素としてのプロジェクトタイトル.プロジェクトの詳細ページにも表示されます.それで、修正しましょうsetProject :
    setProject(project: IProject) {
        // ...
        this.updateJsonSnippet({
          '@type': 'BreadcrumbList',
          itemListElement: [{
              '@type': 'ListItem',
              position: 1,
              name: project.category.value,
              // the url where users can find the list of projects with matching category
              item: this.siteUrl + 'projects?categories=' + project.category.key
          }, {
              '@type': 'ListItem',
              position: 2,
              name: project.title
          }]
        });
    }
    
    最後のビットは、検索結果のプロジェクトの一覧です

    リストの断片


    これも ItemList を返します.だから今我々はこのようなタイトルがあるTop 20 Non smoking cafes in Dubaiそして、我々のページは、それらの20のリストを含んでいます、結果として、約束されるように、アイテムの回転木馬でなければなりません.Googleは、すでに独自の機能を提供していない限り.これはほとんどすべての時間です!
    {
        "@type": "ItemList",
        "itemListElement": [{
            "@type": "ListItem",
            // increasing
            "position": 1,
            // url to result details
            "url": "https://domain.com/projects/32342"
        }]
    }
    
    我々の中でSeoService
    // change this to accept projects array
    setSearchResults(params: IListParams, projects: IProject[]) {
       //...
       // for every element, use params to construct url
       // region.domain.com/language/projects/id
       let i = 1;
       // construct the URL
       const url =this.siteUrl + 'projects/';
    
        this.updateJsonSnippet({
          '@type': 'ItemList',
          // I need to pass projects 
          itemListElement: projects.map(n => {
            return {
              '@type': 'ListItem',
               url: url + n.id,
              position: i++
            }
          }),
        });
    }
    
    その後、検索List プロジェクトのコンポーネント、プロジェクトの結果を渡す
    ngOnInit(): void {
        // search results component
            // ...
            // pass projects results
            this.seoService.setSearchResults(param, projects);
      }
    

    リファクタリングのほとんど


    The SeoService 潜在的に大規模に成長する可能性があります.より大きなプロジェクトでは、機能サービスにスキーマの更新を渡すのはより意味があります.機能のプロパティにアクセスしているので.このアプリでは、私は複数のサービスからの基本を継承にそれを壊すことを選んだSeoService .
    現在、私は複数のサービスを持っています.constructor は複数回呼び出されます.したがって、コンストラクタのすべては既に何かが起こったかどうかチェックする必要があります.
    我々AddTags 関数は、現在のdocument.querySelecor 既にそうする.this.meta.addTags デザインによって、重複を避ける.それで、我々はセットされます.最後を見るStackBlitz project .

    SSR


    BOTSはそれを理解しているので、サーバープラットフォームは提供するより良い選択です、そして、それはスクリプト内容を得るために再水和を待つ必要はありません.
    if (environment.production && this.platform.isBrowser) 
    // do not add scripts in browser
    return;
    
    スクリプトの存在を確認し、再利用することもできます.like we did previously :
    this._jsonSnippet =
          this.doc.querySelector('script[type="application/ld+json"]') ||
          this.createJsonSnippet();
    
    SVRが実装されていない場合、ブラウザプラットフォームはHTMLでスクリプトを蓄積し始める.これはクロールに影響しませんが、ページパフォーマンスに影響する可能性があります.追加emptyJsonSnippet . これはメジャーコンポーネントの前に呼び出されるべきです.
    // SeoService
       protected emptyJsonSnippet() {
        // sometimes, in browser platform, we need to empty objects first
        this._graphObjects = [];
      }
    

    サポートされていない型


    Googleは新しいタイプのサポートを追加します.対象はタイプdocumented on schema.org . まだサポートされていない型がある場合は、schema.org 指示.構造化データを持つGoogleの検索スニペットを超えて他の目的に役立ちます.しかし、ある日、それらのタイプは適切にサポートされます.以下にサポートされていない型の例を示します:
    // not yet supported by Google
     return {
                '@type': 'MedicalEntity', 
                url: url + product.key,
                name: product.name,
                description: product.description,
                image: product.image,
                medicineSystem: 'WesternConventional',
                relevantSpecialty: product.specialties ? product.specialties.map(n => n.name).join(', ') : null
            };
    

    批判


    これをGoogle検索で試してくださいNebula Award for Best Novel 最初の結果はこんな感じです

    今すぐページを開き、スニペットを探します.
    {
        "@context": "https:\/\/schema.org",
        "@type": "Article",
        "name": "Nebula Award for Best Novel",
        "url": "https:\/\/en.wikipedia.org\/wiki\/Nebula_Award_for_Best_Novel",
        "sameAs": "http:\/\/www.wikidata.org\/entity\/Q266012",
        "mainEntity": "http:\/\/www.wikidata.org\/entity\/Q266012",
        "author": {
            "@type": "Organization",
            "name": "Contributors to Wikimedia projects"
        },
        "publisher": {
            "@type": "Organization",
            "name": "Wikimedia Foundation, Inc.",
            "logo": {
                "@type": "ImageObject",
                "url": "https:\/\/www.wikimedia.org\/static\/images\/wmf-hor-googpub.png"
            }
        },
        "datePublished": "2004-01-03T16:06:25Z",
        "dateModified": "2022-04-04T15:53:53Z",
        "image": "https:\/\/upload.wikimedia.org\/wikipedia\/en\/8\/8e\/Nebula_Trophy.jpg",
        "headline": "literary award"
    }
    
    彼らはマッチしますか.ではなく.
    私はしばらくの間、スニペットを研究して、それの多くの批評を読みました.それに対する大きなポイントは、変化する規則です.今日検証するものは、必ずしも来年を検証しません.それに加えて、あなたの場所にあなたのスニペットを持って誓うことができますが、Googleは期待通りに表示しないように選択します.Googleで何が起こるかについては、Googleにとどまります.ボトムライン?スニペットは大丈夫ですが、あいまいです.シンプルで覚えておきましょう.
    Googleはあなたを見つける!
    このポストの底に達してくれてありがとう.あなたがバグや蝶を見つけた場合私に知らせてください.
    資源
  • Google snippets
  • Google structured data gallery
  • Walk through
  • All about featured snippets
  • Schema.org types
  • Understanding JavaScript SEO basics
  • Rich Results testing tool
  • StackBlitz