#Access env variables from client without extra network request.

11 messages · Page 1 of 1 (latest)

crimson mist
crimson mist
#

My idea for now is to embed:

<script type="application/json" id="public_vars">
    {
        "publicVar1": "value1",
        "publicVar2": "value2"
    }
</script>

into resulting html during ssr.
And then use isomorphic function to access public variables.

Isomorphic function would read from process.env in the server and on the client will parse and read that public_vars script content.

But I do not fully understand yet where exactly should i append this script content to the html since all code runs on both sides... And I want to do this only during ssr

fluid furnace
#

Where do you plan to use it? You can use a server function in the root of the app and past to context from beforeLoad, or return from a loader and consume using the useLoaderData from a hook anywhere in the app. You can also set the route loader staleTime to Number.POSITIVE_INFINITY to prevent further refetching. Does this sound like it would solve your issue?

crimson mist
#

Thanks for the answer!
But unfortunately that doesn’t work for me.
I need access variables without extra network request.

I use them in multiple places in the codebase , in multiple files(not necessary react specific like zod schemas and different client configs)

Using in beforeLoad doesn’t work because it is invoked too often and not cached, loader solves that but as I wrote I need to access variables in sync way without network requests. So any async or react specific suspense wait stuff doesn’t work for me. I need to have those variables in the first response client receives - html itself. So scripts which need them will not need to do any async calls.
import.meta.env would be great since it embeds values directly in client bundle. But I can’t use build time vars. I need similar API as import.meta.env to access those vars anywhere in the code.

Since modifying the js bundles on server start doesn’t seem right - the only option I see is embedding it into html and using ScriptOnce to parse and attach to something like window.PublicVars so that I can use isomorphic functions to access them on server and client. Still need to check if it would work.

fluid furnace
crimson mist
#

Okay, I made it work.
The env.ts file:

import { createIsomorphicFn } from "@tanstack/react-start";
import { z } from "zod";

const envSchemaPublic = z.object({
  PUBLIC_VARIABLE1: z.string(),
  PUBLIC_VARIABLE2: z.string(),
});

const PUBLIC_VARS_ID = "__PUBLIC_VARIABLES__";

const injectPublicVars = `window.${PUBLIC_VARS_ID} = JSON.parse(document.getElementById('${PUBLIC_VARS_ID}').textContent)`;

export const getPublicEnv = createIsomorphicFn()
  .server(() => {
    const env = envSchemaPublic.parse(process.env);
    const children = JSON.stringify(env);
    return [
      {
        type: "application/json",
        id: PUBLIC_VARS_ID,
        children,
      },
      {
        children: injectPublicVars,
      },
    ];
  })
  .client(() => []);

export const env = createIsomorphicFn()
  .server(() => envSchemaPublic.parse(process.env))
  // @ts-expect-error we inject public variables into the window object
  .client(() => envSchemaPublic.parse(window[PUBLIC_VARS_ID]))();

And the in __root.tsx for createRootRoute :

export const Route = createRootRoute({
  ...other options
  scripts() {
    return getPublicEnv();
  },
})

In this way the env variable contains all public vars and accessible anywhere in sync way. I can import it in any file and use it on server or client side because it is immediately invoked isomorphic function.

#

scripts execute before react hydration and then just are deleted on client side. So window.__PUBLIC_VARIABLES__ will be populated before any other js code runs

#

Yeah, it looks like monkey patching 😅
But I did not find any other way to do it.

And the DX is the same as with import.meta.env

fluid furnace
#

Hey, what are your thoughts on using this approach to inject Cloudflare variable bindings for client-side usage

crimson mist
# fluid furnace Hey, what are your thoughts on using this approach to inject Cloudflare variable...

If it is possible to use build time vars via import.meta.env - i would choose that. My workaround is just because of limitation that I cannot use build time variables, and it works for nodejs in docker deployment with nitro.

If you have such limitation you can try this approach but you will need to test it yourself, I didn’t try this with cloudflare.

In general my approach with injecting looks bad but because I have no better options - I’m stuck with it. Maybe you can figure out something better for your use case