如何在Angular 16/17中配置apollo-angular实现SSR
Angular 16/17 SSR 配置:解决 Apollo Angular POST 请求的缓存与 Hydration 问题
问题场景
在 Angular 16 或 17 中,使用 provideHttpClient(withFetch()) 配置 HTTP 客户端时,GET 请求的 SSR 缓存与客户端 hydration 工作正常。但搭配 apollo-angular 库时,所有 GraphQL 查询/变更都通过 POST 请求指向同一个 /graphql 端点,导致 SSR 的缓存复用逻辑失效,即使在 Apollo 配置中添加了 ssrMode: true 也无法彻底解决问题。
解决方案
1. 适配 HTTP 传输缓存,支持 GraphQL POST 请求
Angular 内置的 ɵwithHttpTransferCache(内部 API,项目中可安全使用)默认仅缓存 GET 请求,需要自定义配置让它能区分不同的 GraphQL POST 请求:
import { ApplicationConfig } from '@angular/core'; import { provideRouter, withInMemoryScrolling } from '@angular/router'; import { routes } from './app.routes'; import { provideClientHydration } from '@angular/platform-browser'; import { provideHttpClient, withFetch, withInterceptorsFromDi, ɵwithHttpTransferCache } from '@angular/common/http'; import { graphqlProvider } from './graphql.provider'; export const appConfig: ApplicationConfig = { providers: [ provideRouter(routes, withInMemoryScrolling({ anchorScrolling: 'enabled' })), provideClientHydration(), provideHttpClient( withFetch(), withInterceptorsFromDi(), ɵwithHttpTransferCache({ includePostRequests: true, // 基于 URL + 请求体生成唯一缓存键,区分不同 GraphQL 查询 getPostRequestKey: (req) => { if (req.url.endsWith('/graphql')) { return `${req.url}-${JSON.stringify(req.body)}`; } return req.url; } }) ), graphqlProvider ] };
2. 完善 Apollo 客户端的 SSR 专属配置
除了 ssrMode: true,还需根据运行环境动态调整配置,并确保服务端缓存能被客户端复用:
import { Apollo, APOLLO_OPTIONS } from 'apollo-angular'; import { HttpLink } from 'apollo-angular/http'; import { ApplicationConfig, inject, PLATFORM_ID } from '@angular/core'; import { ApolloClientOptions, InMemoryCache } from '@apollo/client/core'; import { isPlatformBrowser } from '@angular/common'; const uri = 'http://localhost:1337/graphql'; export function apolloOptionsFactory(): ApolloClientOptions<any> { const httpLink = inject(HttpLink); const platformId = inject(PLATFORM_ID); const isBrowser = isPlatformBrowser(platformId); return { link: httpLink.create({ uri }), cache: new InMemoryCache(), ssrMode: !isBrowser, // 服务端启用 SSR 模式,客户端禁用 ssrForceFetchDelay: 100, // 确保服务端数据优先加载 // 服务端可添加自定义类型策略优化缓存 ...(!isBrowser ? { cache: new InMemoryCache({ typePolicies: { // 根据你的 GraphQL Schema 补充缓存规则,比如: // Query: { // fields: { // posts: { merge: true } // } // } } }) } : {}) }; } export const graphqlProvider: ApplicationConfig['providers'] = [ Apollo, { provide: APOLLO_OPTIONS, useFactory: apolloOptionsFactory, }, ];
3. 实现服务端缓存状态序列化与客户端恢复
- 服务端(server.ts):渲染页面时将 Apollo 缓存注入到 HTML 中
import { APP_BASE_HREF } from '@angular/common'; import { CommonEngine } from '@angular/ssr'; import express from 'express'; import { fileURLToPath } from 'node:url'; import { dirname, join, resolve } from 'node:path'; import AppServerModule from './src/main.server'; import { Apollo } from 'apollo-angular'; function bootstrap(): Promise<express.Express> { const server = express(); const serverDistFolder = dirname(fileURLToPath(import.meta.url)); const browserDistFolder = resolve(serverDistFolder, '../browser'); const indexHtml = join(serverDistFolder, 'index.server.html'); const commonEngine = new CommonEngine(); server.set('view engine', 'html'); server.set('views', browserDistFolder); server.get('*.*', express.static(browserDistFolder, { maxAge: '1y' })); server.get('*', async (req, res, next) => { const { protocol, originalUrl, baseUrl, headers } = req; try { const html = await commonEngine.render({ bootstrap: AppServerModule, documentFilePath: indexHtml, url: `${protocol}://${headers.host}${originalUrl}`, publicPath: browserDistFolder, providers: [{ provide: APP_BASE_HREF, useValue: baseUrl }], }); // 提取 Apollo 缓存并注入到页面 const apollo = commonEngine.injector.get(Apollo); const apolloState = JSON.stringify(apollo.client.cache.extract()); const finalHtml = html.replace( '</body>', `<script>window.__APOLLO_STATE__ = ${apolloState};</script></body>` ); res.send(finalHtml); } catch (err) { next(err); } }); return Promise.resolve(server); } bootstrap().then(server => { server.listen(process.env['PORT'] || 4000, () => { console.log(`Node Express server listening on http://localhost:${process.env['PORT'] || 4000}`); }); });
- 客户端(app.component.ts):页面加载时恢复 Apollo 缓存
import { Component, PLATFORM_ID, inject, OnInit } from '@angular/core'; import { isPlatformBrowser } from '@angular/common'; import { Apollo } from 'apollo-angular'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit { private apollo = inject(Apollo); private platformId = inject(PLATFORM_ID); ngOnInit(): void { if (isPlatformBrowser(this.platformId)) { const apolloState = window['__APOLLO_STATE__']; if (apolloState) { this.apollo.client.restore(apolloState); } } } }
验证方式
- 启动 SSR 服务后,查看页面源代码,确认存在
window.__APOLLO_STATE__变量 - 客户端加载时,检查 Network 面板,确认
/graphql的 POST 请求不会重复发送(hydration 成功时会复用服务端缓存) - 查看浏览器控制台,确保没有 hydration 相关错误
内容的提问来源于stack exchange,提问作者nishantk
相关产品推荐
相关产品推荐

