Skip to content

Commit 7e51838

Browse files
authored
[ENG-8545] Google attempting to index preprint fails with "soft 404" or 5xx responses (#739)
- Ticket: [ENG-8545] - Feature flag: n/a ## Summary of Changes 1. Added prerender ready service.
1 parent a8f8715 commit 7e51838

File tree

11 files changed

+88
-11
lines changed

11 files changed

+88
-11
lines changed

src/@types/global.d.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,5 +47,11 @@ declare global {
4747
*/
4848
loggedIn: boolean;
4949
};
50+
51+
/**
52+
* Flag used by prerender services to determine when a page is fully loaded.
53+
* Set to false initially, then set to true once all AJAX requests and content are loaded.
54+
*/
55+
prerenderReady?: boolean;
5056
}
5157
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { inject, Injectable } from '@angular/core';
2+
3+
import { WINDOW } from '../provider/window.provider';
4+
5+
@Injectable({
6+
providedIn: 'root',
7+
})
8+
export class PrerenderReadyService {
9+
private readonly window = inject(WINDOW);
10+
11+
setNotReady(): void {
12+
if (this.window && 'prerenderReady' in this.window) {
13+
this.window.prerenderReady = false;
14+
}
15+
}
16+
17+
setReady(): void {
18+
if (this.window && 'prerenderReady' in this.window) {
19+
this.window.prerenderReady = true;
20+
}
21+
}
22+
}

src/app/features/preprints/mappers/preprints.mapper.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ export class PreprintsMapper {
8080
preprintDoiLink: response.links.preprint_doi,
8181
articleDoiLink: response.links.doi,
8282
embeddedLicense: null,
83+
providerId: response.relationships?.provider?.data?.id,
8384
};
8485
}
8586

@@ -92,6 +93,7 @@ export class PreprintsMapper {
9293
): PreprintModel {
9394
const data = response.data;
9495
const links = response.data.links;
96+
const relationships = response?.data?.relationships;
9597

9698
return {
9799
id: data.id,
@@ -137,6 +139,7 @@ export class PreprintsMapper {
137139
identifiers: IdentifiersMapper.fromJsonApi(data.embeds?.identifiers),
138140
preprintDoiLink: links.preprint_doi,
139141
articleDoiLink: links.doi,
142+
providerId: relationships?.provider?.data?.id,
140143
};
141144
}
142145

src/app/features/preprints/models/preprint.models.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ export interface PreprintModel {
4646
preprintDoiLink?: string;
4747
articleDoiLink?: string;
4848
identifiers?: IdentifierModel[];
49+
providerId: string;
4950
}
5051

5152
export interface PreprintFilesLinks {

src/app/features/preprints/pages/preprint-details/preprint-details.component.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,11 +122,11 @@ describe('PreprintDetailsComponent', () => {
122122
value: false,
123123
},
124124
{
125-
selector: ContributorsSelectors.getContributors,
125+
selector: ContributorsSelectors.getBibliographicContributors,
126126
value: mockContributors,
127127
},
128128
{
129-
selector: ContributorsSelectors.isContributorsLoading,
129+
selector: ContributorsSelectors.isBibliographicContributorsLoading,
130130
value: false,
131131
},
132132
{

src/app/features/preprints/pages/preprint-details/preprint-details.component.ts

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { ActivatedRoute, Router, RouterLink } from '@angular/router';
2525

2626
import { ENVIRONMENT } from '@core/provider/environment.provider';
2727
import { HelpScoutService } from '@core/services/help-scout.service';
28+
import { PrerenderReadyService } from '@core/services/prerender-ready.service';
2829
import { ClearCurrentProvider } from '@core/store/provider';
2930
import { UserSelectors } from '@core/store/user';
3031
import { ResetState } from '@osf/features/files/store';
@@ -101,6 +102,7 @@ export class PreprintDetailsComponent implements OnInit, OnDestroy {
101102
private readonly datePipe = inject(DatePipe);
102103
private readonly dataciteService = inject(DataciteService);
103104
private readonly analyticsService = inject(AnalyticsService);
105+
private readonly prerenderReady = inject(PrerenderReadyService);
104106

105107
private readonly environment = inject(ENVIRONMENT);
106108

@@ -124,8 +126,8 @@ export class PreprintDetailsComponent implements OnInit, OnDestroy {
124126
preprint = select(PreprintSelectors.getPreprint);
125127
preprint$ = toObservable(select(PreprintSelectors.getPreprint));
126128
isPreprintLoading = select(PreprintSelectors.isPreprintLoading);
127-
contributors = select(ContributorsSelectors.getContributors);
128-
areContributorsLoading = select(ContributorsSelectors.isContributorsLoading);
129+
contributors = select(ContributorsSelectors.getBibliographicContributors);
130+
areContributorsLoading = select(ContributorsSelectors.isBibliographicContributorsLoading);
129131
reviewActions = select(PreprintSelectors.getPreprintReviewActions);
130132
areReviewActionsLoading = select(PreprintSelectors.arePreprintReviewActionsLoading);
131133
withdrawalRequests = select(PreprintSelectors.getPreprintRequests);
@@ -138,6 +140,7 @@ export class PreprintDetailsComponent implements OnInit, OnDestroy {
138140
areMetricsLoading = select(PreprintSelectors.arePreprintMetricsLoading);
139141

140142
isPresentModeratorQueryParam = toSignal(this.route.queryParams.pipe(map((params) => params['mode'] === 'moderator')));
143+
defaultProvider = this.environment.defaultProvider;
141144

142145
moderationMode = computed(() => {
143146
const provider = this.preprintProvider();
@@ -170,6 +173,7 @@ export class PreprintDetailsComponent implements OnInit, OnDestroy {
170173

171174
constructor() {
172175
this.helpScoutService.setResourceType('preprint');
176+
this.prerenderReady.setNotReady();
173177

174178
effect(() => {
175179
const currentPreprint = this.preprint();
@@ -178,6 +182,16 @@ export class PreprintDetailsComponent implements OnInit, OnDestroy {
178182
this.analyticsService.sendCountedUsage(currentPreprint.id, 'preprint.detail').subscribe();
179183
}
180184
});
185+
186+
effect(() => {
187+
const preprint = this.preprint();
188+
const contributors = this.contributors();
189+
const isLoading = this.isPreprintLoading() || this.areContributorsLoading();
190+
191+
if (!isLoading && preprint && contributors.length) {
192+
this.setMetaTags();
193+
}
194+
});
181195
}
182196

183197
private preprintWithdrawableState = computed(() => {
@@ -257,7 +271,7 @@ export class PreprintDetailsComponent implements OnInit, OnDestroy {
257271
);
258272
});
259273

260-
isOsfPreprint = computed(() => this.providerId() === 'osf');
274+
isOsfPreprint = computed(() => this.providerId() === this.defaultProvider);
261275

262276
moderationStatusBannerVisible = computed(() => {
263277
return (
@@ -351,6 +365,8 @@ export class PreprintDetailsComponent implements OnInit, OnDestroy {
351365
}
352366

353367
fetchPreprint(preprintId: string) {
368+
this.prerenderReady.setNotReady();
369+
354370
this.actions.fetchPreprintById(preprintId).subscribe({
355371
next: () => {
356372
this.checkAndSetVersionToTheUrl();
@@ -370,8 +386,6 @@ export class PreprintDetailsComponent implements OnInit, OnDestroy {
370386
});
371387
}
372388
}
373-
374-
this.setMetaTags();
375389
},
376390
});
377391
}

src/app/features/project/overview/project-overview.component.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { takeUntilDestroyed, toObservable } from '@angular/core/rxjs-interop';
2323
import { FormsModule } from '@angular/forms';
2424
import { ActivatedRoute, NavigationEnd, Router, RouterLink } from '@angular/router';
2525

26+
import { PrerenderReadyService } from '@core/services/prerender-ready.service';
2627
import { SubmissionReviewStatus } from '@osf/features/moderation/enums';
2728
import {
2829
ClearCollectionModeration,
@@ -123,6 +124,7 @@ export class ProjectOverviewComponent implements OnInit {
123124
private readonly dataciteService = inject(DataciteService);
124125
private readonly metaTags = inject(MetaTagsService);
125126
private readonly datePipe = inject(DatePipe);
127+
private readonly prerenderReady = inject(PrerenderReadyService);
126128

127129
submissions = select(CollectionsModerationSelectors.getCollectionSubmissions);
128130
collectionProvider = select(CollectionsSelectors.getCollectionProvider);
@@ -271,6 +273,8 @@ export class ProjectOverviewComponent implements OnInit {
271273
readonly analyticsService = inject(AnalyticsService);
272274

273275
constructor() {
276+
this.prerenderReady.setNotReady();
277+
274278
this.setupCollectionsEffects();
275279
this.setupCleanup();
276280
this.setupProjectEffects();

src/app/features/registry/registry.component.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { takeUntilDestroyed, toObservable, toSignal } from '@angular/core/rxjs-i
88
import { ActivatedRoute, RouterOutlet } from '@angular/router';
99

1010
import { ENVIRONMENT } from '@core/provider/environment.provider';
11+
import { PrerenderReadyService } from '@core/services/prerender-ready.service';
1112
import { ClearCurrentProvider } from '@core/store/provider';
1213
import { pathJoin } from '@osf/shared/helpers/path-join.helper';
1314
import { AnalyticsService } from '@osf/shared/services/analytics.service';
@@ -33,6 +34,7 @@ export class RegistryComponent implements OnDestroy {
3334
private readonly destroyRef = inject(DestroyRef);
3435
private readonly route = inject(ActivatedRoute);
3536
private readonly environment = inject(ENVIRONMENT);
37+
private readonly prerenderReady = inject(PrerenderReadyService);
3638

3739
private readonly actions = createDispatchMap({
3840
getRegistryById: GetRegistryById,
@@ -47,6 +49,8 @@ export class RegistryComponent implements OnDestroy {
4749
readonly analyticsService = inject(AnalyticsService);
4850

4951
constructor() {
52+
this.prerenderReady.setNotReady();
53+
5054
effect(() => {
5155
if (this.registryId()) {
5256
this.actions.getRegistryById(this.registryId());

src/app/shared/services/meta-tags.service.ts

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { catchError, map, Observable, of, switchMap, tap } from 'rxjs';
22

33
import { DOCUMENT } from '@angular/common';
4-
import { DestroyRef, Inject, inject, Injectable } from '@angular/core';
4+
import { DestroyRef, effect, Inject, inject, Injectable, signal } from '@angular/core';
55
import { Meta, MetaDefinition, Title } from '@angular/platform-browser';
66

77
import { ENVIRONMENT } from '@core/provider/environment.provider';
8+
import { PrerenderReadyService } from '@core/services/prerender-ready.service';
89

910
import { MetadataRecordFormat } from '../enums/metadata-record-format.enum';
1011
import { HeadTagDef } from '../models/meta-tags/head-tag-def.model';
@@ -19,6 +20,7 @@ import { MetadataRecordsService } from './metadata-records.service';
1920
export class MetaTagsService {
2021
private readonly metadataRecords: MetadataRecordsService = inject(MetadataRecordsService);
2122
private readonly environment = inject(ENVIRONMENT);
23+
private readonly prerenderReady = inject(PrerenderReadyService);
2224

2325
get webUrl() {
2426
return this.environment.webUrl;
@@ -52,11 +54,19 @@ export class MetaTagsService {
5254

5355
private metaTagStack: { metaTagsData: MetaTagsData; componentDestroyRef: DestroyRef }[] = [];
5456

57+
areMetaTagsApplied = signal(false);
58+
5559
constructor(
5660
private meta: Meta,
5761
private title: Title,
5862
@Inject(DOCUMENT) private document: Document
59-
) {}
63+
) {
64+
effect(() => {
65+
if (this.areMetaTagsApplied()) {
66+
this.prerenderReady.setReady();
67+
}
68+
});
69+
}
6070

6171
updateMetaTags(metaTagsData: MetaTagsData, componentDestroyRef: DestroyRef): void {
6272
this.metaTagStack = [...this.metaTagStackWithout(componentDestroyRef), { metaTagsData, componentDestroyRef }];
@@ -71,6 +81,8 @@ export class MetaTagsService {
7181
const elementsToRemove = this.document.querySelectorAll(`.${this.metaTagClass}`);
7282

7383
if (elementsToRemove.length === 0) {
84+
this.areMetaTagsApplied.set(false);
85+
this.prerenderReady.setNotReady();
7486
return;
7587
}
7688

@@ -81,6 +93,8 @@ export class MetaTagsService {
8193
});
8294

8395
this.title.setTitle(String(this.defaultMetaTags.siteName));
96+
this.areMetaTagsApplied.set(false);
97+
this.prerenderReady.setNotReady();
8498
}
8599

86100
private metaTagStackWithout(destroyRefToRemove: DestroyRef) {
@@ -97,6 +111,8 @@ export class MetaTagsService {
97111
}
98112

99113
private applyMetaTagsData(metaTagsData: MetaTagsData) {
114+
this.areMetaTagsApplied.set(false);
115+
this.prerenderReady.setNotReady();
100116
const combinedData = { ...this.defaultMetaTags, ...metaTagsData };
101117
const headTags = this.getHeadTags(combinedData);
102118
of(metaTagsData.osfGuid)
@@ -113,8 +129,11 @@ export class MetaTagsService {
113129
)
114130
: of(null)
115131
),
116-
tap(() => this.applyHeadTags(headTags)),
117-
tap(() => this.dispatchZoteroEvent())
132+
tap(() => {
133+
this.applyHeadTags(headTags);
134+
this.areMetaTagsApplied.set(true);
135+
this.dispatchZoteroEvent();
136+
})
118137
)
119138
.subscribe();
120139
}

src/index.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
<script src="assets/js/newrelic/newrelic.snippet.js"></script>
1212
</head>
1313
<body>
14+
<script>
15+
window.prerenderReady = false;
16+
</script>
1417
<osf-root></osf-root>
1518
</body>
1619
</html>

0 commit comments

Comments
 (0)