apollo-angular
apollo-angular copied to clipboard
Server Side Rendering Stalls when Apollo fails to return the responses from the server.
I am trying to add Server Side Rendering (SSR) to my app. Angular-Apollo is working great in the regular app, but in server side rendering, it is simply never returning the results of a the queries and causing the render to stall. I have an HttpInterceptor for adding headers and I can see that the query results are being returned, but they are not processed any further by Apollo and my application render dies.
It might be important to note that I am using GraphQL Codegen to wrap the various queries, and that I'm running two queries, a page query and a country query, neither of which return.
My GraphQL Module
import {NgModule, InjectionToken} from '@angular/core';
import {
TransferState,
makeStateKey,
BrowserTransferStateModule,
} from '@angular/platform-browser';
import {HttpClientModule, HTTP_INTERCEPTORS} from '@angular/common/http';
// Apollo
import {APOLLO_OPTIONS} from 'apollo-angular';
import {HttpLink} from 'apollo-angular/http';
import {InMemoryCache} from '@apollo/client/core';
import { PageInterceptor } from './services/page.interceptor';
import { getEnv } from '@web-env/helper';
const APOLLO_CACHE = new InjectionToken<InMemoryCache>('apollo-cache');
const STATE_KEY = makeStateKey<any>('apollo.state');
@NgModule({
imports: [
HttpClientModule,
BrowserTransferStateModule,
],
providers: [
{
provide: APOLLO_CACHE,
useValue: new InMemoryCache(),
},
{
provide: APOLLO_OPTIONS,
useFactory(
httpLink: HttpLink,
cache: InMemoryCache,
transferState: TransferState,
) {
const isBrowser = transferState.hasKey<any>(STATE_KEY);
if (isBrowser) {
const state = transferState.get<any>(STATE_KEY, null);
cache.restore(state);
} else {
transferState.onSerialize(STATE_KEY, () => {
return cache.extract();
});
}
const env = getEnv();
return {
link: httpLink.create({uri: env.graphql}),
cache,
//connectToDevTools: !env.production,
ssrMode: !isBrowser,
};
},
deps: [HttpLink, APOLLO_CACHE, TransferState],
},
{
provide: HTTP_INTERCEPTORS,
useClass: PageInterceptor,
multi: true,
},
],
})
export class GraphQLModule {}
My HttpInterceptor
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from "@angular/common/http";
import { Injectable } from "@angular/core";
import { Observable, throwError } from 'rxjs';
import { catchError, switchMap, tap } from "rxjs/operators";
import { PageService } from "./page.service";
@Injectable({
providedIn: 'root'
})
export class PageInterceptor implements HttpInterceptor {
constructor(
private pageService: PageService
) {}
intercept( req: HttpRequest<any>, next: HttpHandler ): Observable<HttpEvent<any>> {
const id = new Date().getTime();
this.pageService.setHttpActive(id);
return this.pageService.headers.pipe(
switchMap( setHeaders => next.handle(req.clone({setHeaders})) ),
tap( () => this.pageService.clearHttpActive(id) ),
tap( console.log ),
catchError( err => {
this.pageService.clearHttpActive(id);
return throwError(err);
})
);
}
}
Here is the CountryGQL Query Generated by GraphQL Codegen
export const CountryDocument = gql`
query country {
country: clientCountry {
location
country
eu
shop
}
}
`;
@Injectable({
providedIn: 'root'
})
export class CountryGQL extends Apollo.Query<CountryQuery, CountryQueryVariables> {
document = CountryDocument;
constructor(apollo: Apollo.Apollo) {
super(apollo);
}
}
├── @angular-devkit/[email protected]
├── @angular/[email protected]
├── @angular/[email protected]
├── @apollo/[email protected]
├── [email protected]
├── [email protected]
├── [email protected]
└── [email protected]
Additional context
Unfortunately I am not seeing any error messages in the command line. I've tried using the debugger, but I haven't gotten anywhere with that either.
Again the code base works great in the live server, and production builds, but the SSR server is just stalling.
I believe I am running into this one as well. Works fine if using ng serve
, some additional info though, it only hangs on cold page loads. For example:
- spin up
ng serve-ssr
- load page -> hangs (forever)
- refresh browser page
- Page will load this time and any subsequent times
- Can confirm that when I remove the gql from being used in universal the page loads on cold start no problems
Having optimization and enableProdMode
on or off does not seem to make a difference.
We are rendering our Angular universal apps in an AWS Lambda function so depending on traffic the lambda function may have spun down and will cold start again thus causing the page to hang.
Found the issue, we were using a subscription
in a couple places and assumed that ssrMode: true
would handle it but we were only providing an http
link (no link for subscriptions) so something was just hanging. What worked for us was to do something along the lines of this:
const ws = isBrowser ? createClient() : () => void // no-op function in the case of the server
const options = {
provide: AURORA_NAMED_CLIENT_OPTIONS,
useFactory: (httpLink, platformId, transferState) => ({ ssrMode: isPlatformServer(platformId), link: split(({ query }) => {
const definition = getMainDefinition(query);
return definition.kind === 'OperationDefinition' && definition.operation === 'subscription';
}, ws, http)}),
deps: [HttpLink, PLATFORM_ID, [new Optional(), TransferState]],
}
Having the same issue now with @angular/*
as soon as Apollo
is injected anywhere in the application, it never gets stable on the client within 10000ms. This is reproducible with an application setup from scratch (Angular 17.1.3), providing APOLLO_OPTIONS
and e.g. injecting Apollo
in the AppComponent.
Same issue with SSR. Hydration takes ~10 secs after the initial page load if Apollo
is injected in any component that is rendered
This issue lacks a minimal (no codegen) reproduction case with latest Angular, using application builder, to be properly investigated.
@PowerKiKi I've made a repo with reproducible example on the latest angular version
The results are the following for the browser. ApplicationRef.isStable
took ~10sec.
For the server:
I've managed to find out the issue.
...So the problem is in ApolloClient
from @apollo/client/core
. There's an option in the client called connectToDevTools
, which triggers the devtools connect that uses setTimeout
function for 10000ms.
I've removed the hydration stall by setting connectToDevTools
to false
. However, despite the claim that this option value is false in production mode the issue is reproducible when calling ng build && node dist/app-name/server/server.mjs
Not sure if this issue is fixable from apollo-angular side
I've managed to find out the issue.
...So the problem is in
ApolloClient
from@apollo/client/core
. There's an option in the client calledconnectToDevTools
, which triggers the devtools connect that usessetTimeout
function for 10000ms.I've removed the hydration stall by setting
connectToDevTools
tofalse
. However, despite the claim that this option value is false in production mode the issue is reproducible when callingng build && node dist/app-name/server/server.mjs
Not sure if this issue is fixable from apollo-angular side
Interesting, maybe I have another issue going on because I get the hydration (stability) stalling even when connectToDevTools
is disabled.
I've managed to find out the issue.
...So the problem is in
ApolloClient
from@apollo/client/core
. There's an option in the client calledconnectToDevTools
, which triggers the devtools connect that usessetTimeout
function for 10000ms.I've removed the hydration stall by setting
connectToDevTools
tofalse
. However, despite the claim that this option value is false in production mode the issue is reproducible when callingng build && node dist/app-name/server/server.mjs
Not sure if this issue is fixable from apollo-angular side
This fixed my issue!