Skip to content

Commit 88045a5

Browse files
petebacondarwinmhevery
authored andcommitted
feat(aio): update metatags to control search engine crawling (#21665)
The `<meta name="robots" content="noindex">` tag is used to indicate to search engine crawlers that they should not index the current page. This is set dynamically by the the document viewer component to ensure that 404 and other erroring pages are not added to the search index. This relies upon the idea that the crawling bot will run the JS and wait to see if this meta tag has been added or not. Since we believe that the `googebot` will do this, we also pre-emptively add a hard-coded noindex tag specifically for this bot, so that if anything else fails in bootstrapping the app, the failed page will not be added to the index. Closes #21317 PR Close #21665
1 parent 0b38a03 commit 88045a5

File tree

5 files changed

+77
-4
lines changed

5 files changed

+77
-4
lines changed

aio/e2e/app.e2e-spec.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,20 @@ describe('site App', function() {
128128
});
129129

130130
describe('404 page', () => {
131+
it('should add or remove the "noindex" meta tag depending upon the validity of the page', () => {
132+
page.navigateTo('');
133+
expect(element(by.css('meta[name="googlebot"]')).isPresent()).toBeFalsy();
134+
expect(element(by.css('meta[name="robots"]')).isPresent()).toBeFalsy();
135+
136+
page.navigateTo('does/not/exist');
137+
expect(element(by.css('meta[name="googlebot"][content="noindex"]')).isPresent()).toBeTruthy();
138+
expect(element(by.css('meta[name="robots"][content="noindex"]')).isPresent()).toBeTruthy();
139+
140+
page.getTopMenuLink('features').click();
141+
expect(element(by.css('meta[name="googlebot"]')).isPresent()).toBeFalsy();
142+
expect(element(by.css('meta[name="robots"]')).isPresent()).toBeFalsy();
143+
});
144+
131145
it('should search the index for words found in the url', () => {
132146
page.navigateTo('http/router');
133147
const results = page.getSearchResults();

aio/src/app/layout/doc-viewer/doc-viewer.component.spec.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { ComponentRef } from '@angular/core';
22
import { ComponentFixture, TestBed } from '@angular/core/testing';
3-
import { Title } from '@angular/platform-browser';
3+
import { Title, Meta } from '@angular/platform-browser';
44

55
import { Observable } from 'rxjs/Observable';
66
import { of } from 'rxjs/observable/of';
77

8+
import { FILE_NOT_FOUND_ID, FETCHING_ERROR_ID } from 'app/documents/document.service';
89
import { EmbedComponentsService } from 'app/embed-components/embed-components.service';
910
import { Logger } from 'app/shared/logger.service';
1011
import { TocService } from 'app/shared/toc.service';
@@ -413,6 +414,24 @@ describe('DocViewerComponent', () => {
413414
await doRender('Qux content');
414415
expect(addTitleAndTocSpy).toHaveBeenCalledTimes(4);
415416
});
417+
418+
it('should remove "noindex" meta tags if the document is valid', async () => {
419+
await doRender('foo', 'bar');
420+
expect(TestBed.get(Meta).removeTag).toHaveBeenCalledWith('name="googlebot"');
421+
expect(TestBed.get(Meta).removeTag).toHaveBeenCalledWith('name="robots"');
422+
});
423+
424+
it('should add "noindex" meta tags if the document is 404', async () => {
425+
await doRender('missing', FILE_NOT_FOUND_ID);
426+
expect(TestBed.get(Meta).addTag).toHaveBeenCalledWith({ name: 'googlebot', content: 'noindex' });
427+
expect(TestBed.get(Meta).addTag).toHaveBeenCalledWith({ name: 'robots', content: 'noindex' });
428+
});
429+
430+
it('should add "noindex" meta tags if the document fetching fails', async () => {
431+
await doRender('error', FETCHING_ERROR_ID);
432+
expect(TestBed.get(Meta).addTag).toHaveBeenCalledWith({ name: 'googlebot', content: 'noindex' });
433+
expect(TestBed.get(Meta).addTag).toHaveBeenCalledWith({ name: 'robots', content: 'noindex' });
434+
});
416435
});
417436

418437
describe('(embedding components)', () => {
@@ -538,6 +557,8 @@ describe('DocViewerComponent', () => {
538557
expect(logger.output.error).toEqual([
539558
[`[DocViewer] Error preparing document 'foo': ${error.stack}`],
540559
]);
560+
expect(TestBed.get(Meta).addTag).toHaveBeenCalledWith({ name: 'googlebot', content: 'noindex' });
561+
expect(TestBed.get(Meta).addTag).toHaveBeenCalledWith({ name: 'robots', content: 'noindex' });
541562
});
542563

543564
it('when `EmbedComponentsService.embedInto()` fails', async () => {
@@ -557,6 +578,8 @@ describe('DocViewerComponent', () => {
557578
expect(logger.output.error).toEqual([
558579
[`[DocViewer] Error preparing document 'bar': ${error.stack}`],
559580
]);
581+
expect(TestBed.get(Meta).addTag).toHaveBeenCalledWith({ name: 'googlebot', content: 'noindex' });
582+
expect(TestBed.get(Meta).addTag).toHaveBeenCalledWith({ name: 'robots', content: 'noindex' });
560583
});
561584

562585
it('when `destroyEmbeddedComponents()` fails', async () => {
@@ -576,6 +599,8 @@ describe('DocViewerComponent', () => {
576599
expect(logger.output.error).toEqual([
577600
[`[DocViewer] Error preparing document 'baz': ${error.stack}`],
578601
]);
602+
expect(TestBed.get(Meta).addTag).toHaveBeenCalledWith({ name: 'googlebot', content: 'noindex' });
603+
expect(TestBed.get(Meta).addTag).toHaveBeenCalledWith({ name: 'robots', content: 'noindex' });
579604
});
580605

581606
it('when `swapViews()` fails', async () => {
@@ -595,6 +620,8 @@ describe('DocViewerComponent', () => {
595620
expect(logger.output.error).toEqual([
596621
[`[DocViewer] Error preparing document 'qux': ${error.stack}`],
597622
]);
623+
expect(TestBed.get(Meta).addTag).toHaveBeenCalledWith({ name: 'googlebot', content: 'noindex' });
624+
expect(TestBed.get(Meta).addTag).toHaveBeenCalledWith({ name: 'robots', content: 'noindex' });
598625
});
599626

600627
it('when something fails with non-Error', async () => {
@@ -611,6 +638,8 @@ describe('DocViewerComponent', () => {
611638
expect(logger.output.error).toEqual([
612639
[`[DocViewer] Error preparing document 'qux': ${error}`],
613640
]);
641+
expect(TestBed.get(Meta).addTag).toHaveBeenCalledWith({ name: 'googlebot', content: 'noindex' });
642+
expect(TestBed.get(Meta).addTag).toHaveBeenCalledWith({ name: 'robots', content: 'noindex' });
614643
});
615644
});
616645

aio/src/app/layout/doc-viewer/doc-viewer.component.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Component, ComponentRef, DoCheck, ElementRef, EventEmitter, Input, OnDestroy, Output } from '@angular/core';
2-
import { Title } from '@angular/platform-browser';
2+
import { Title, Meta } from '@angular/platform-browser';
33

44
import { Observable } from 'rxjs/Observable';
55
import { of } from 'rxjs/observable/of';
@@ -9,7 +9,7 @@ import 'rxjs/add/operator/do';
99
import 'rxjs/add/operator/switchMap';
1010
import 'rxjs/add/operator/takeUntil';
1111

12-
import { DocumentContents } from 'app/documents/document.service';
12+
import { DocumentContents, FILE_NOT_FOUND_ID, FETCHING_ERROR_ID } from 'app/documents/document.service';
1313
import { EmbedComponentsService } from 'app/embed-components/embed-components.service';
1414
import { Logger } from 'app/shared/logger.service';
1515
import { TocService } from 'app/shared/toc.service';
@@ -72,6 +72,7 @@ export class DocViewerComponent implements DoCheck, OnDestroy {
7272
private embedComponentsService: EmbedComponentsService,
7373
private logger: Logger,
7474
private titleService: Title,
75+
private metaService: Meta,
7576
private tocService: TocService
7677
) {
7778
this.hostElement = elementRef.nativeElement;
@@ -141,6 +142,8 @@ export class DocViewerComponent implements DoCheck, OnDestroy {
141142
protected render(doc: DocumentContents): Observable<void> {
142143
let addTitleAndToc: () => void;
143144

145+
this.setNoIndex(doc.id === FILE_NOT_FOUND_ID || doc.id === FETCHING_ERROR_ID);
146+
144147
return this.void$
145148
// Security: `doc.contents` is always authored by the documentation team
146149
// and is considered to be safe.
@@ -156,10 +159,24 @@ export class DocViewerComponent implements DoCheck, OnDestroy {
156159
const errorMessage = (err instanceof Error) ? err.stack : err;
157160
this.logger.error(`[DocViewer] Error preparing document '${doc.id}': ${errorMessage}`);
158161
this.nextViewContainer.innerHTML = '';
162+
this.setNoIndex(true);
159163
return this.void$;
160164
});
161165
}
162166

167+
/**
168+
* Tell search engine crawlers whether to index this page
169+
*/
170+
private setNoIndex(val: boolean) {
171+
if (val) {
172+
this.metaService.addTag({ name: 'googlebot', content: 'noindex' });
173+
this.metaService.addTag({ name: 'robots', content: 'noindex' });
174+
} else {
175+
this.metaService.removeTag('name="googlebot"');
176+
this.metaService.removeTag('name="robots"');
177+
}
178+
}
179+
163180
/**
164181
* Swap the views, removing `currViewContainer` and inserting `nextViewContainer`.
165182
* (At this point all content should be ready, including having loaded and instantiated embedded

aio/src/index.html

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,13 @@
3131
<meta name="apple-mobile-web-app-capable" content="yes">
3232
<meta name="apple-mobile-web-app-status-bar-style" content="translucent">
3333

34+
<!--
35+
Initially tell the Google crawler not to index this page.
36+
If the page loads correctly will remove this tag (in the DocViewer).
37+
Subsequent navigations will update the tag dynamically (i.e. soft 404).
38+
Don't do the same for `robots` in general here, since they might not be able to handle the tag changing dynamically.
39+
-->
40+
<meta name="googlebot" content="noindex">
3441

3542
<!-- Google Analytics -->
3643
<script>

aio/src/testing/doc-viewer-utils.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Component, ComponentRef, NgModule, ViewChild } from '@angular/core';
2-
import { Title } from '@angular/platform-browser';
2+
import { Title, Meta } from '@angular/platform-browser';
33

44
import { Observable } from 'rxjs/Observable';
55

@@ -51,6 +51,11 @@ export class MockTitle {
5151
setTitle = jasmine.createSpy('Title#reset');
5252
}
5353

54+
export class MockMeta {
55+
addTag = jasmine.createSpy('Meta#addTag');
56+
removeTag = jasmine.createSpy('Meta#removeTag');
57+
}
58+
5459
export class MockTocService {
5560
genToc = jasmine.createSpy('TocService#genToc');
5661
reset = jasmine.createSpy('TocService#reset');
@@ -65,6 +70,7 @@ export class MockTocService {
6570
{ provide: Logger, useClass: MockLogger },
6671
{ provide: EmbedComponentsService, useClass: MockEmbedComponentsService },
6772
{ provide: Title, useClass: MockTitle },
73+
{ provide: Meta, useClass: MockMeta },
6874
{ provide: TocService, useClass: MockTocService },
6975
],
7076
})

0 commit comments

Comments
 (0)