nitro icon indicating copy to clipboard operation
nitro copied to clipboard

Support cloudflare environment variables

Open pi0 opened this issue 2 years ago β€’ 1 comments

Reference docs: https://developers.cloudflare.com/workers/platform/environment-variables/

Related: https://github.com/nuxt/framework/issues/5056

Normally, we allow extending runtime config using Node.js environment variables (process.env.FOO) while cloudflare exposes env as global constant variables (accessible as FOO or globalThis.FOO).

We can add support for globalThis but since without prefix can be dangerous, I propose to support ENV_* variables for globalThis support. (Setting ENV_FOO in Cloudflare is same as FOO=x node when using Node.js) but the downside is makes usage harder and needing more docs. We might only enable prefixless support for Cloudflare.

pi0 avatar Jun 08 '22 20:06 pi0

I'm not sure if it will change anything for this issue, but the new module workers receive their environment variables differently. They are no longer bound to the global scope and are instead passed as the second argument to the fetch handler. I've been keeping notes on how to add support in this discussion. It might not change anything, but it's also noteworthy that any change here might be very short-lived as the new esm-based modules format is required in order to use any of the new features of Cloudflare Workers like D1 (SQL Database) and Durable Objects.

marshallswain avatar Jun 28 '22 03:06 marshallswain

Since D1 hit public alpha, I've been trying to get something happening based on @marshallswain's discussion.

This WIP ESM preset is working, but as discussed, needs a way to access the environment to use service bindings, D1 etc: https://github.com/timhanlon/nitro/commit/fabba1fefb8cba24fb49b9b104954c7b6d088d69

I'm keen to help push this forward, but unsure of an approach that would work for Nitro, given Cloudflare is one of many supported providers.

timhanlon avatar Nov 19 '22 00:11 timhanlon

This looks really promising with the ESM worker. I wonder if this can also be ported over to the Cloudflare Pages version too?

These types of things are going to be a game changer to get for example vuefire-nuxt working (eventually and maybe πŸ™ˆ).

I also ran into the situation with the process.env not rendering in the frontend but being needed by other plugins / etc.

I think a workaround with namespaced cloudflare env => process.env. mapping would be great.

I quickly tested it locally with a patched version of nitropack and it did exactly what it needed to do.

chrisspiegl avatar Mar 23 '23 10:03 chrisspiegl

I like the idea of binding to process.env as well so it can stay consistent as in development.

But we need to find also a way to make it work for runtimeConfig

Atinux avatar Apr 10 '23 13:04 Atinux

With CF worker format, we have another limitation that ENV is only accessible within a request. For this we might introduce useRuntimeConfig?(event) (also subsequently event.context.env)

pi0 avatar Apr 10 '23 17:04 pi0

With CF worker format, we have another limitation that ENV is only accessible within a request. For this we might introduce useRuntimeConfig?(event) (also subsequently event.context.env)

I'm hitting this issue now and it's really inconvenient ... For now I'm working around this issue by modifying my runtime code to do this :

  let apiKey = useRuntimeConfig().openaiApiKey as string
  if (apiKey.length === 0) {
    apiKey = event.context.cloudflare.env.NUXT_OPENAI_API_KEY
  }

For the module syntax and pages, we could unblock this quite easily by doing something like this in the Nitro runtime cloudflare presets :

setRuntimeConfig(env)

setRuntimeConfig would proxy the variables like env.NUXT_MY_VARIABLE as runtimeConfig.myVariable (we could also proxy NITRO_* by convention, or everything ?)

This doesn't work with service workers syntax, but we should deprecate it anyways, pretty sure there's no need to maintain both syntaxes in Nitro

Then forwarding the request to Nitro where user code would work as expected with useRuntimeConfig.

Another things that is cloudflare specific that we could do is copy the user .env file into a .dev.vars as it's needed for wrangler to load the environment variables locally.

That way everything would work both in dev and while testing the build locally. Then for deploy the variables needs to bet set manually in the dashboard or with wrangler secrets put (workers only, doesn't work with pages), so we could display a message to the user that they should do that.

I can open a PR with this change @pi0

Hebilicious avatar Jun 16 '23 13:06 Hebilicious

@Hebilicious fix is needed (please merge his pr)

lx358hcl avatar Jul 22 '23 15:07 lx358hcl

When will this be fixed, any ideas? @pi0

lx358hcl avatar Jul 22 '23 16:07 lx358hcl

When will this be fixed, any ideas? @pi0

There's a PR with a fix #1318

Hebilicious avatar Jul 22 '23 20:07 Hebilicious

When will this be fixed, any ideas? @pi0

There's a PR with a fix #1318

Please merge itπŸ™Œ

lx358hcl avatar Jul 22 '23 23:07 lx358hcl

Hi, are there any updates concerning this issue? Thanks and kind regards, Davide

DidoMarchet avatar Aug 04 '23 07:08 DidoMarchet

@DidoMarchet you can use process.env.MY_ENV directly inside your server routes and it should works

Atinux avatar Aug 04 '23 08:08 Atinux

With CF worker format, we have another limitation that ENV is only accessible within a request. For this we might introduce useRuntimeConfig?(event) (also subsequently event.context.env)

I'm hitting this issue now and it's really inconvenient ... For now I'm working around this issue by modifying my runtime code to do this :

  let apiKey = useRuntimeConfig().openaiApiKey as string
  if (apiKey.length === 0) {
    apiKey = event.context.cloudflare.env.NUXT_OPENAI_API_KEY
  }

For the module syntax and pages, we could unblock this quite easily by doing something like this in the Nitro runtime cloudflare presets :

setRuntimeConfig(env)

setRuntimeConfig would proxy the variables like env.NUXT_MY_VARIABLE as runtimeConfig.myVariable (we could also proxy NITRO_* by convention, or everything ?)

This doesn't work with service workers syntax, but we should deprecate it anyways, pretty sure there's no need to maintain both syntaxes in Nitro

Then forwarding the request to Nitro where user code would work as expected with useRuntimeConfig.

Another things that is cloudflare specific that we could do is copy the user .env file into a .dev.vars as it's needed for wrangler to load the environment variables locally.

That way everything would work both in dev and while testing the build locally. Then for deploy the variables needs to bet set manually in the dashboard or with wrangler secrets put (workers only, doesn't work with pages), so we could display a message to the user that they should do that.

I can open a PR with this change @pi0

Edit : After running further tests, it turns out that useRuntimeConfig(event) already works with cloudflare, cloudflare_module and cloudflare_pages thanks to unenv https://github.com/unjs/unenv/pull/95 : useRuntimeConfig(event) will fallback to values in the global scope which will work consistently with nitro dev and nitro build. However this won't work without unenv, therefore I updated #1318 to reflect that. I will also make a separated PR to update the docs, and I believe this issue can be closed.

NITRO_OPENAI_API_KEY=abc #useRuntimeConfig(event).openaiApiKey will work with nuxt and nitro standalone
NUXT_OPENAI_API_KEY=abc #useRuntimeConfig(event).openaiApiKey will work with nuxt 
OPENAI_API_KEY=abc #useRuntimeConfig(event).openaiApiKey will not work

@lukamo1996 @DidoMarchet could you confirm that useRuntimeConfig(event) works for you ?

Hebilicious avatar Aug 07 '23 10:08 Hebilicious

so.. if I am using

    sanity: {
      token: process.env.SANITY_TOKEN,
    },
    auth0: {
      clientID: process.env.Auth0__ClientId,
      redirectUrl: process.env.Auth0__RedirectUri,
      audience: process.env.Auth0__Audience,
      domain: process.env.Auth0__Domain,
      logoutReturnTo: process.env.Auth0__LogoutReturn,
    },
    public: {
      apiUrl: process.env.API_URL || 'https://localhost:5001',
      siteUrl: process.env.NUXT_PUBLIC_SITE_URL || 'http://localhost:4500',
      featureFlags: {
        mapFilters: process.env.FEATURE_MAPFILTERS === 'true',
      },
      googleAnalytics: {
        id: process.env.GOOGLE_ANALYTICS_ID,
        enabled: false,
      },
    },
  },

what do I do for cases that I do not have an event, or how does this work out for the client?

beaudryj avatar Aug 14 '23 02:08 beaudryj

@beaudryj this will work, see working example here : https://github.com/Hebilicious/nuxt-authjs-google

Hebilicious avatar Aug 14 '23 06:08 Hebilicious

I had that, but it does not seem to work. I want to note that i do my preset build in my CI and ship my dist directory to cloudflare. If i was building on cloudflare do the envvars get set on their wokers at a build time? Or does nitro set the values there on page request

beaudryj avatar Aug 15 '23 02:08 beaudryj

I had that, but it does not seem to work. I want to note that i do my preset build in my CI and ship my dist directory to cloudflare. If i was building on cloudflare do the envvars get set on their wokers at a build time? Or does nitro set the values there on page request

You need to manually set the variables, you can use wrangler or the dashboard, see instructions in PR here https://github.com/unjs/nitro/pull/1547/files

Hebilicious avatar Aug 15 '23 07:08 Hebilicious

// In development
export default defineEventHandler((event) => {
  useRuntimeConfig(event).helloThere //general
  useRuntimeConfig(event).secret //undefined
  // Module syntax (cloudflare_module, cloudflare_pages)
  event.context.cloudflare.env.NITRO_HELLO_THERE //general
  event.context.cloudflare.env.SECRET //secret
  // Service worker syntax (cloudflare)
  NITRO_HELLO_THERE //general
  SECRET //secret
});

this block you have here though would not work for pages without events? what do I do in those scenarios

or is it more my nuxt.config.ts should be setup as

      apiUrl: event.context.cloudflare.env.API_URL || 'https://localhost:5001',
      siteUrl: event.context.cloudflare.env.NUXT_PUBLIC_SITE_URL || 'http://localhost:4500',
      showFeedback: event.context.cloudflare.env.SHOW_FEEDBACK === 'true',
      logRocketId: event.context.cloudflare.env.LOGROCKET_ID,
      pubnubPublishKey:event.context.cloudflare.env.PUBNUB_PUBLISH_KEY,
      pubnubSubscribeKey: event.context.cloudflare.env.PUBNUB_SUBSCRIBE_KEY,
      featureFlags: {
        mapFilters: event.context.cloudflare.env.FEATURE_MAPFILTERS === 'true',
      },

beaudryj avatar Aug 15 '23 14:08 beaudryj

// In development
export default defineEventHandler((event) => {
  useRuntimeConfig(event).helloThere //general
  useRuntimeConfig(event).secret //undefined
  // Module syntax (cloudflare_module, cloudflare_pages)
  event.context.cloudflare.env.NITRO_HELLO_THERE //general
  event.context.cloudflare.env.SECRET //secret
  // Service worker syntax (cloudflare)
  NITRO_HELLO_THERE //general
  SECRET //secret
});

this block you have here though would not work for pages without events? what do I do in those scenarios

or is it more my nuxt.config.ts should be setup as

      apiUrl: event.context.cloudflare.env.API_URL || 'https://localhost:5001',
      siteUrl: event.context.cloudflare.env.NUXT_PUBLIC_SITE_URL || 'http://localhost:4500',
      showFeedback: event.context.cloudflare.env.SHOW_FEEDBACK === 'true',
      logRocketId: event.context.cloudflare.env.LOGROCKET_ID,
      pubnubPublishKey:event.context.cloudflare.env.PUBNUB_PUBLISH_KEY,
      pubnubSubscribeKey: event.context.cloudflare.env.PUBNUB_SUBSCRIBE_KEY,
      featureFlags: {
        mapFilters: event.context.cloudflare.env.FEATURE_MAPFILTERS === 'true',
      },

You don't need to do that,process.env will work thanks to unenv. However, you have to set the env variables with wrangler or the cf dashboard.

Hebilicious avatar Aug 15 '23 14:08 Hebilicious

I have them set in the dashboard. Build my projects with terraform and they are setup on the project I am deploying to. And I am saying that process.env in my runtimeConfig is not working. I deploy my latest to my configured project and the cloudflare variables are not being pulled. image

I have values set on both Production and Preview

beaudryj avatar Aug 15 '23 15:08 beaudryj

I have them set in the dashboard. Build my projects with terraform and they are setup on the project I am deploying to. And I am saying that process.env in my runtimeConfig is not working. I deploy my latest to my configured project and the cloudflare variables are not being pulled. image

I have values set on both Production and Preview

Hard to tell more without a reproduction, but I think your variables need to be prefixed by NUXT_ or NITRO_ in your nitro.config.ts/ nuxt.config.ts

https://nuxt.com/docs/guide/going-further/runtime-config#environment-variables https://nitro.unjs.io/guide/configuration#update-runtime-config-using-environment-variables

Hebilicious avatar Aug 15 '23 18:08 Hebilicious

image neither made a difference.

in my

server route sample:

server/routes/signin.js

import { createNonce } from '~/utils/auth';
import buildUrl from 'build-url';
import { defineEventHandler, setCookie } from 'h3';

export default defineEventHandler(async (event) => {
  const runtimeConfig = useRuntimeConfig(event);
  const nonce = createNonce();
  setCookie(event, 'nonce', nonce, { secure: true });

  const url = buildUrl(`https://${runtimeConfig.auth0.domain}`, {
    path: 'authorize',
    queryParams: {
      client_id: runtimeConfig.auth0.clientID,
      response_type: '',
      redirect_uri: runtimeConfig.auth0.redirectUrl,
      scope: '',
      audience: runtimeConfig.auth0.audience,
      nonce: nonce,
    },
  });

  return sendRedirect(event, url, 302);
});

app.vue sample


<template>
  <NuxtLayout>
    <NuxtLoadingIndicator />
    <NuxtPage />
  </NuxtLayout>
</template>

<script setup lang="ts">
const route = useRoute();
const runtimeConfig = useRuntimeConfig();
const domain = runtimeConfig.public.domain;

useHead({
  titleTemplate: '%s - Leagued',
  viewport: 'width=device-width, initial-scale=1',
  charset: 'utf-8',
  htmlAttrs: {
    lang: 'en',
  },
  link: [
    {
      rel: 'apple-touch-icon',
      sizes: '180x180',
      href: '/favicon/apple-touch-icon.png',
    },
    {
      rel: 'icon',
      type: 'image/png',
      sizes: '32x32',
      href: '/favicon/favicon-32x32.png',
    },
    {
      rel: 'manifest',
      type: 'image/png',
      sizes: '16x16',
      href: '/favicon/favicon-16x16.png',
    },
    { rel: 'manifest', href: '/favicon/site.webmanifest' },
    {
      rel: 'mask-icon',
      color: '#5bbad5',
      href: '/favicon/safari-pinned-tab.svg',
    },
    { rel: 'shortcut icon', href: '/favicon/favicon.ico' },
    { name: 'msapplication-TileColor', content: '#ffc40d' },
    { name: 'msapplication-config', href: '/favicon/browserconfig.xml' },
    { name: 'theme-color', content: '#ffffff' },
    {
      rel: 'canonical',
      href: () => `https://example.com${route.path}`,
    },
  ],
});
</script>


beaudryj avatar Aug 16 '23 02:08 beaudryj

@beaudryj If you are confident that there's a bug, I recommend that you open an issue in the Nuxt repository with a minimal reproduction attached.

Hebilicious avatar Aug 16 '23 05:08 Hebilicious

@Hebilicious thank you for the back and fourth on this.

I found what was tripping us up - https://v2.nuxt.com/docs/configuration-glossary/configuration-env/#processenv--

further digging into the docs I noticed this -

export default {
 runtimeConfig: {
    apiKey: '' // Default to an empty string, automatically set at runtime using process.env.NUXT_API_KEY
    public: {
       baseURL: '' // Exposed to the frontend as well.
    }
  }
}

So I did a deployment using Default to an empty string, automatically set at runtime using process.env.NUXT_API_KEY and it seems to have resolved

beaudryj avatar Aug 20 '23 01:08 beaudryj