如何为 ApolloClient 设置环境变量,该变量应该在 CI/CD 的服务器端呈现

     2023-03-10     61

关键词:

【中文标题】如何为 ApolloClient 设置环境变量,该变量应该在 CI/CD 的服务器端呈现【英文标题】:How to set environment variable for ApolloClient that should be server side rendered for CI/CD 【发布时间】:2021-12-30 15:48:01 【问题描述】:

我有以下 apolloClient

/**
 * Initializes an ApolloClient instance. For configuration values refer to the following page
 * https://www.apollographql.com/docs/react/api/core/ApolloClient/#the-apolloclient-constructor
 *
 * @returns ApolloClient
 */
const createApolloClient = (authToken: string | null) => 
  const httpLinkHeaders = 
    ...(authToken &&  Authorization: `Bearer $authToken` )
  ;
  console.log('CREATING APOLLO CLIENT WITH HEADERS >>>>', httpLinkHeaders);

  console.log(
    'Graph Env Variable URL >>>>>',
    publicRuntimeConfig.GRAPHQL_URL
  );

  

  return new ApolloClient(
    name: 'client',
    s-s-rMode: typeof window === 'undefined',
    link: createHttpLink(
      uri: publicRuntimeConfig.GRAPHQL_URL,
      credentials: 'same-origin',
      headers: httpLinkHeaders
    ),
    cache: new InMemoryCache()
  );
;

/**
 * Initializes the apollo client with data restored from the cache for pages that fetch data
 * using either getStaticProps or getServerSideProps methods
 *
 * @param accessToken
 * @param initialState
 *
 * @returns ApolloClient
 */
export const initializeApollo = (
  accessToken: string,
  initialState = null,
  forceNewInstane = false
): ApolloClient<NormalizedCacheObject> => 
  // Regenerate client?
  if (forceNewInstane) 
    apolloClient = null;
  

  const _apolloClient = apolloClient || createApolloClient(accessToken);

  // for pages that have Next.js data fetching methods that use Apollo Client,
  // the initial state gets hydrated here
  if (initialState) 
    // Get existing cache, loaded during client side data fetching
    const existingCache = _apolloClient.extract();

    // Restore the cache using the data passed from
    // getStaticProps/getServerSideProps combined with the existing cached data
    _apolloClient.cache.restore( ...existingCache, ...initialState );
  

  // For SSG and s-s-r always create a new Apollo Client
  if (typeof window === 'undefined') return _apolloClient;

  // Create the Apollo Client once in the client
  if (!apolloClient) apolloClient = _apolloClient;
  return _apolloClient;
;

/**
 * Hook to initialize the apollo client only when state has changed.
 *
 * @param initialState
 *
 * @returns
 */
export const useApollo = (
  initialState: any
): ApolloClient<NormalizedCacheObject> => 
  return useMemo(() => 
    if (process.browser) 
      const accessToken = extractCookie(document.cookie, 'access_token');
      return initializeApollo(accessToken, initialState);
    

    // document is not present and we can't retrieve the token but ApolloProvider requires to pass a client
    return initializeApollo(null, initialState);
  , [initialState]);
;

在 _app.tsx 文件中这样初始化

const updateApolloWithNewToken = useCallback(
    (accessToken: string) => 
      // Initialize apollo client with new access token
      setClient(
        initializeApollo(accessToken, pageProps.initialApolloState, true)
      );
      // Show the dashboard
      router.replace('/dashboard');
    ,
    [router]
  );

使用下面的下一个配置

const  PHASE_DEVELOPMENT_SERVER  = require('next/constants');

module.exports = (phase,  defaultConfig ) => 
  console.log('Phase >>>>', phase);
  if (phase === PHASE_DEVELOPMENT_SERVER) 
    console.log('RETURNING DEVELOPMENT CONFIGURATION...');

    return 
      publicRuntimeConfig: 
        
        GRAPHQL_URL: process.env.GRAPHQL_URL
      
    ;
  

  console.log('RETURNING PRODUCTION CONFIGURATION...');

  console.log('GRAPHQL_URL', process.env.GRAPHQL_URL);

  return 
    publicRuntimeConfig: 
      
      GRAPHQL_URL: process.env.GRAPHQL_URL
    
  ;
;

这是我的 _app.tsx

function MyApp( Component, pageProps : AppProps) 
  // Grab the apollo client instance with state hydration from the pageProps
  const router = useRouter();
  const apolloClient = useApollo(pageProps.initialApolloState);
  const [client, setClient] = useState(apolloClient);

  
  React.useEffect(() => 
    // Refresh token on browser load or page regresh
    handleAcquireTokenSilent();

    // We also set up an interval of 5 mins to check if token needs to be refreshed
    const refreshTokenInterval = setInterval(() => 
      handleAcquireTokenSilent();
    , REFRESH_TOKEN_SILENTLY_INTERVAL);

    return () => 
      clearInterval(refreshTokenInterval);
    ;
  , []);

  
  const updateApolloWithNewToken = useCallback(
    (accessToken: string) => 
      // Initialize apollo client with new access token
      setClient(
        initializeApollo(accessToken, pageProps.initialApolloState, true)
      );
      // Show the dashboard
      router.replace('/dashboard');
    ,
    [router]
  );

  return pageProps.isAuthenticated || pageProps.shouldPageHandleUnAuthorize ? (
    <ApolloProvider client=client>
      <ThemeProvider theme=theme>
        <SCThemeProvider theme=theme>
          <StylesProvider injectFirst>
            <Component
              ...pageProps
              updateAuthToken=updateApolloWithNewToken
            />
          </StylesProvider>
        </SCThemeProvider>
      </ThemeProvider>
    </ApolloProvider>
  ) : (
    <UnAuthorize />
  );


/**
 * Fetches the Me query so that pages/components can grab it from the cache in the
 * client.
 *
 * Note: This disables the ability to perform automatic static optimization, causing
 * every page in the app to be server-side rendered.
 */
MyApp.getInitialProps = async (appContext: AppContext) => 
  const appProps = await App.getInitialProps(appContext);
  const req = appContext.ctx.req;

  // Execute Me query and handle all scenarios including, unauthorized response, caching data so pages can grab
  // data from the cache
  return await handleMeQuery(appProps, req);
;

export default MyApp;

我的问题是,当我运行 yarn build 时,我得到一个生成 500 页面的服务器错误。我知道这是因为在创建 Apollo Client 时它无法访问 publicRuntimeConfig,看起来 Next 在我运行 yarn build 时正在尝试构建 ApolloClient,我正在使用 getInitialProps 和 getServerSideProps 所以我只想访问所有运行时而不是构建时的 env 变量,因为我们想要为我们的管道构建一个版本。

我的应用程序中使用 publicRuntimeConfig 的所有其他环境变量都在工作,我通过在构建时删除环境变量并在启动时重新添加它们进行测试,并且应用程序正常运行。

如果没有办法使用 apollo 客户端执行此操作,建议在应用程序启动时将不同的 uri 传递为 env 变量,而不是为 Apollo 客户端或替代解决方案构建?

感谢您提前提供的任何帮助

所以我不知道我是否已经充分解释了这个问题。

基本上,graphql URL 根据它在开发、登台和生产中所处的环境而有所不同,但是它们应该使用相同的构建,所以我需要通过运行时变量访问 GRAPHQL_URL,但在我当前的设置中只是未定义。

【问题讨论】:

【参考方案1】:

首先,存在冗余代码和低效率。主要是钩子updateApolloWithNewTokenuseApollo,还有你注入accessToken 的方式。

我建议将ApolloClient 放入它自己的单独文件中,并为您的用例使用可配置的links。

但是,真正的问题很可能在于客户端的初始化以及您对memoizing 客户端的尝试。

首先,尝试更新以下内容,

    link: createHttpLink(
      uri: publicRuntimeConfig.GRAPHQL_URL,
      credentials: 'same-origin',
      headers: httpLinkHeaders
    ),

  link: createHttpLink(
    // ...your other stuff
    uri: () => getConfig().publicRuntimeConfig.GRAPHQL_URL
  )

如果这不能立即奏效,我建议尝试使用最简单的示例。

创建一个您导出的客户端、一个提供程序和一个使用它的组件(不使用 state、useEffect 或其他任何东西)。如果还是不行,我们可以从那里开始。

【讨论】:

【参考方案2】:

通常你 don't want to use publicRuntimeConfig 因为它增加了开销并且对你的用例来说是不必要的。它还会禁用自动静态优化。

传统上,环境变量是根据环境处理动态设置的方法。 Next.js 有三个default environments——开发、测试和生产。

您的 Next.js 应用将根据环境自动抓取(并合并)正确的变量。

.env.local

GRAPHQL_URL = localhost:8000/graphql

.env.test

GRAPHQL_URL = test.example.com/graphql

.env 或 .env.production

GRAPHQL_URL = production.example.com/graphql

阿波罗配置

new ApolloClient(
 link: createHttpLink(
  uri: process.env.GRAPHQL_URL,
 )
);

环境变量文件

在您的项目根目录中,您可以创建名为

的文件 .env .env.local .env.test .env.test.local .env.production

我相信您可以将 .local 附加到任何环境以创建仅限本地的版本 .env.production.local - 这在您的情况下用途有限


Next.js 环境变量

所有环境 - .env

在开发、测试和生产环境中加载。此文件用于所有环境的默认值。如果另一个环境变量文件具有同名变量,则此文件中的变量将被覆盖。

Development environment.env.local

仅在NODE_ENV = development(下一个开发者)时加载

Test environment.env.test

仅在NODE_ENV = test 时加载

Test environment.env.test.local

仅在 NODE_ENV = test 和本地时加载

生产环境.env.production

仅在NODE_ENV = production(下次启动)时加载

示例生产变量

创建一个.env.production 并在其中添加变量。您可以对测试变量和仅局部变量重复相同的操作。


注意事项

将 .env 添加到您的 .gitignore 是一种很好的做法,这样您就不会意外地将秘密提交到您的存储库。至少你应该从 git 中省略 .env*.local 文件。

根据您的 CI/CD 设置,您可以在部署平台中设置环境变量,如 vercel、github 操作等。这将允许您在托管平台而不是代码中设置测试和生产变量。

如果你需要一个环境变量accessible in the browser,你需要在变量前加上NEXT_PUBLIC_

【讨论】:

环境变量被注入,因此与运行时不兼容(它们无法更改)。该问题要求运行时(如果需要,我猜是替代解决方案)。如果您不关心运行时注入,我也建议您这样做。 这是一种比 next.js 中的运行时变量更标准的方法。 Example production only variable 部分专门回答“建议在应用程序启动时而不是在构建时将不同的 uri 作为 env 变量传递” @Anders Kitson 你能澄清一下你是否想要改变环境变量的能力或者只是想要拥有不同变量的能力。照原样,肖恩的回答会要求您每次更改环境时都重新构建。 我需要 GRAPHQL_URL 的变量在运行时在 azure 的应用程序设置中是可更改的,因为如果它是一个构建,我们只做一个构建,这将导致每个阶段的开发、登台和生产都有相同的网址。我会在你的回答中尝试你的策略@urmzd 谢谢并报告 每个阶段都有自己的变量(graphql url)定义在相应的环境变量文件中。您只运行一个构建,Next 会根据阶段自动获取正确的环境变量。您的根目录中将有 3 个 .env 文件 - 每个阶段一个(见帖子)。 Next.js 不推荐使用 publicRuntimeConfig 来处理环境变量。检查帖子中的第一个链接。

如何为反应应用程序设置环境变量?

】如何为反应应用程序设置环境变量?【英文标题】:Howtosetuptheenvvariableforthereactapp?【发布时间】:2021-05-2714:45:14【问题描述】:我的react应用程序在http://localhost:3000上运行,我想为不同的环境开发、生产、登台和本地设置env变... 查看详情

如何为 csh/tcsh 中的一个命令设置环境变量

】如何为csh/tcsh中的一个命令设置环境变量【英文标题】:Howtosetanenvironmentvariableforjustonecommandincsh/tcsh【发布时间】:2011-08-2205:59:21【问题描述】:在bash中,我可以为一个命令设置一个临时环境变量,如下所示:LD_LIBRARY_PATH=/foo/b... 查看详情

如何为 OSX Mountain Lion 上的应用程序设置环境变量?

】如何为OSXMountainLion上的应用程序设置环境变量?【英文标题】:HowtosetenvironmentvariablestoanapplicationonOSXMountainLion?【发布时间】:2012-08-2307:44:31【问题描述】:自从升级到OSXMountainLion后,我在为eclipse和maven设置环境变量时遇到了... 查看详情

如何为 rspec 设置 ENV 变量?

】如何为rspec设置ENV变量?【英文标题】:HowdoIgetanENVvariablesetforrspec?【发布时间】:2013-05-2618:13:48【问题描述】:我正在使用工头来启动我的Rails开发服务器。很高兴我可以将我所有的环境变量放在.env文件中。有没有办法为我的... 查看详情

如何为 dev vs prod @ionic/app-scripts 设置动态环境变量?

】如何为devvsprod@ionic/app-scripts设置动态环境变量?【英文标题】:HowcanIsetadynamicenvvariablefordevvsprod@ionic/app-scripts?【发布时间】:2018-09-1416:14:08【问题描述】:我在ionic/webpack构建过程中完全是个菜鸟,但是对于这个任务,我特意... 查看详情

如何为 Firefox 设置 Selenium Python 环境

】如何为Firefox设置SeleniumPython环境【英文标题】:HowtosetupaSeleniumPythonenvironmentforFirefox【发布时间】:2017-07-0111:43:03【问题描述】:?我正在使用Firefox50、Selenium3、Python3.5。我尝试了很多二进制文件并在环境变量PATH中复制geckodrive... 查看详情

如何为android开发设置eclipse?

】如何为android开发设置eclipse?【英文标题】:Howtoseteclipseforandroiddevelopment?【发布时间】:2018-06-2912:51:18【问题描述】:我刚刚下载了javaJDK8并设置了JDK和JRE的环境变量,下载了androidSDK直接解压到C:(没有平台工具所以我使用... 查看详情

windows下如何为jdk配置环境变量

jdk下载地址:https://www.oracle.com/technetwork/java/javase/archive-139210.html安装好了jdk后,我们还需要为java配置环境变量。第一步,计算机属性——高级系统设置;第二步,新建一个名为JAVA_HOME的系统变量,第二栏的值即为你自己jdk的安... 查看详情

如何为python的虚拟环境设置特定的python版本? [复制]

】如何为python的虚拟环境设置特定的python版本?[复制]【英文标题】:Howtosetupspecificpythonversionforvirtualenvironmentforpython?[duplicate]【发布时间】:2021-05-0208:10:06【问题描述】:我是python的新手,我正在尝试为我的项目添加一个新环境... 查看详情

如何为 Angular 2.0 中的数字设置语言环境

】如何为Angular2.0中的数字设置语言环境【英文标题】:Howtosetlocalefornumbersinangular2.0【发布时间】:2016-10-0715:37:24【问题描述】:瑞士德语中的数字格式类似于“100\'000.00”(不是“100,000.00”)。我该如何改变呢?我尝试将number_p... 查看详情

如何为包含空格的 Jenkins 环境变量指定值

】如何为包含空格的Jenkins环境变量指定值【英文标题】:HowtospecifyavalueforaJenkinsenvironmentvariablethatcontainsaspace【发布时间】:2013-07-0813:20:30【问题描述】:我正在尝试为包含空格的Jenkins环境变量(在管理Jenkins->配置系统屏幕上... 查看详情

如何为 Vue Apollo 导入 Apollo Client 3?

】如何为VueApollo导入ApolloClient3?【英文标题】:HowtoImportApolloClient3forVueApollo?【发布时间】:2020-12-0623:49:56【问题描述】:Vue-Apollo3.04的发行说明声明现在支持ApolloClient3。正如ApolloClient3的文档所声明的那样,ApolloClient、InMemoryCach... 查看详情

如何为 AWS ECS 任务定义提供环境变量?

】如何为AWSECS任务定义提供环境变量?【英文标题】:howtoprovideenvironmentvariablestoAWSECStaskdefinition?【发布时间】:2017-09-0522:00:26【问题描述】:在ECS的任务定义中,我提供了如下环境变量:作为HOST_NAME的键和作为something.cloud.com的... 查看详情

如何为 [Setup] AppVersion 使用编译时环境变量?

】如何为[Setup]AppVersion使用编译时环境变量?【英文标题】:Howtousecompile-timeenvironmentvariablesfor[Setup]AppVersion?【发布时间】:2020-12-1802:28:18【问题描述】:我正在使用InnoSetup6(Windows10、VisualStudioCode和WSL作为我的shell)来创建安装... 查看详情

symfony:如何为不同的环境设置配置参数文件?

】symfony:如何为不同的环境设置配置参数文件?【英文标题】:symfony:Howtosetconfigurationparametersfilesfordifferentenvironments?【发布时间】:2015-06-0209:42:36【问题描述】:如何为每个环境设置不同的配置参数文件?目前parameters.yml中的参... 查看详情

如何为 xcode 添加全局包含路径

】如何为xcode添加全局包含路径【英文标题】:Howtoaddaglobalincludepathforxcode【发布时间】:2010-10-1910:34:06【问题描述】:我想在使用Xcode时将~/include添加到all项目的包含路径中,例如在Linux中设置环境变量CPLUS_INCLUDE_PATH。(有关Linux... 查看详情

如何为 Angular 2.0 设置环境?

】如何为Angular2.0设置环境?【英文标题】:HowtosetuptheenvironmentforAngular2.0?【发布时间】:2017-05-2500:10:00【问题描述】:向Angular2.0迈出第一步。首先要为开发搭建合适的环境。我的index.html<!DOCTYPEHTML><html><head><title&g... 查看详情

Android:如何为参数变量设置默认值

】Android:如何为参数变量设置默认值【英文标题】:Android:Howtosetadefaultvalueforanargumentvariable【发布时间】:2011-07-3116:22:24【问题描述】:安卓功能PHP示例:functionHaHa($a="Test")print$a;问题是如何在android中做到这一点......publicvoidsomeFu... 查看详情