You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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);
      }
    }
  }
}

验证方式

  1. 启动 SSR 服务后,查看页面源代码,确认存在 window.__APOLLO_STATE__ 变量
  2. 客户端加载时,检查 Network 面板,确认 /graphql 的 POST 请求不会重复发送(hydration 成功时会复用服务端缓存)
  3. 查看浏览器控制台,确保没有 hydration 相关错误

内容的提问来源于stack exchange,提问作者nishantk

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 05:07:22