Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using shared variables with no clientPrefix #228

Open
julius-retzer opened this issue May 3, 2024 · 7 comments
Open

Using shared variables with no clientPrefix #228

julius-retzer opened this issue May 3, 2024 · 7 comments

Comments

@julius-retzer
Copy link

Hello, thank you for your effort on this nice library.

According to this issue #192, when we don't want to use the client prefix, we should use the shared envs. However, even when I do that, I'm getting Attempted to access a server-side environment variable on the client when on client.

There seems to be this piece of code, which seems to completely prevent using the env variable on client:

   const isServerAccess = (prop)=>{
        if (!opts.clientPrefix)
            return true;
        return !prop.startsWith(opts.clientPrefix) && !(prop in shared.shape);
    }

Is this a bug or am I doing something wrong?

@chungweileong94
Copy link
Contributor

@julius-retzer If you are working in the server-only environment, the server option is all you need, and you can just ignore the clientPrefix & client options. However, one gotcha is that you shouldn't set clientPrefix: "", as it will treat all variables as client variables. If you intended to use t3-env in the client environment, you almost certainly have to use clientPrefix, as most of the frameworks do that, even create-react-app.

The share option is more for special cases like NODE_ENV [reference], where you can access the env from both server & client, without any client prefix.

There seems to be this piece of code, which seems to completely prevent using the env variable on client:

   const isServerAccess = (prop)=>{
        if (!opts.clientPrefix)
            return true;
        return !prop.startsWith(opts.clientPrefix) && !(prop in shared.shape);
    }

This code always assumes that you are in a server environment if the clientPrefix is not set. There might be chances where t3-env wasn't able to tell if you are in the server environment or not, if that's the case you can override the check via the isServer option.

Best if you could provide a repro, easy to check if it's a bug or some configuration mistake🙂

@ivan-kleshnin
Copy link

ivan-kleshnin commented Sep 12, 2024

I'm also puzzled by this.

  1. What "server" and "client" namespaces mean precisely? Is React RSC a "server" or a "client"? Is NextJS middleware a "server"? I assume it's all "server" because it's executed in NodeJS / Edge but it would be great not to guess or try-n-error but read in the docs (in the first paragraph of the first section).
  2. If "server can access client vars" what is the difference between "client" and "shared"? Is it just about prefixes being enforced?

I was able to access vars on the client with:

export default createEnv({
  clientPrefix: "PUBLIC_", // any non-empty prefix, used ONLY to satisfy the library
  client: {},              // not a fan of long vars and magic, so I list everything explicitly below
  shared: {
    NODE_ENV: z.enum(["development", "production", "test"]),
    FIRST_VAR: z.any(),
    SECOND_VAR: z.any(),
    ...
  }  
})

// + pass through to Webpack config in `next.config` file

The following does not work:

export default createEnv({
  clientPrefix: "", 
  client: {
    NODE_ENV: z.enum(["development", "production", "test"]),
    FIRST_VAR: z.any(),
    SECOND_VAR: z.any(),
    ...
  },
})

with Attempted to access a server-side environment variable on the client as mentioned by the topic starter.
Why it's suddendly a "server side variable"? 👀

@chungweileong94
Copy link
Contributor

chungweileong94 commented Sep 13, 2024

  1. What "server" and "client" namespaces mean precisely? Is React RSC a "server" or a "client"? Is NextJS middleware a "server"? I assume it's all "server" because it's executed in NodeJS / Edge but it would be great not to guess or try-n-error but read in the docs (in the first paragraph of the first section).

It depends on which framework you are working on, but they are mostly the same. Everything that runs on NodeJS/Edge Runtime are consider server, others that get bundle into client code (code that runs on browser) are consider client.

  1. If "server can access client vars" what is the difference between "client" and "shared"? Is it just about prefixes being enforced?

client + clientPrefix is obviously for us to define the client-only variables, they usually start with a specific prefix (depends on framework). However, there are some variables like NODE_ENV, where they exist in both server and client, even without client prefix. So without making the t3-core harder to maintain and complex, we added shared section, where variables that defined in shared will not be checked against clientPrefix.

The point of having client + clientPrefix is to enforce people to use the correct client env variable naming, just like what we supposed to do in vanilla framework-way, and also to prevent us from accessing the non-client env from client.

@chungweileong94
Copy link
Contributor

with Attempted to access a server-side environment variable on the client as mentioned by the topic starter.
Why it's suddendly a "server side variable"? 👀

When you try to access a variable via t3-env from client, it will always check if the name is start with clientPrefix or fall under shared section, else it will throw Attempted to access a server-side environment variable on the client.

@ivan-kleshnin
Copy link

ivan-kleshnin commented Sep 13, 2024

Thank you for the detailed explanation 🤝 So shared is my choice if I don't want to use any common prefix.

In NextJS it's pretty easy to prevent bundling like this:

// next.config.mjs
...
let {sharedEnv} = await jiti.import("./env.ts")

let withBundleAnalyzer = ...

let nextConfig = withBundleAnalyzer({
  ...
  env: sharedEnv, // a bit simpltified, e.g. NODE_ENV should be dropped
})

so I don't get the purpose of a framework-specific ENV naming. It feels bad to declare the same var as, for example, NEXT_PUBLIC_APP_ORIGIN in NextJS and APP_ORIGIN elsewhere.

@chungweileong94
Copy link
Contributor

chungweileong94 commented Sep 13, 2024

so I don't get the purpose of a framework-specific ENV naming. It feels bad to declare the same var as, for example, NEXT_PUBLIC_APP_ORIGIN in NextJS and APP_ORIGIN elsewhere.

It's mainly because browser doesn't have the concept of ENV in general, that's why frameworks like NextJS, required us to use the NEXT_PUBLIC_ prefix, so that it can bundle the env value directly into the client code. And t3-env just inherits the same concept and further prevents people from leaking the server env.

Personally, I would just declare NEXT_PUBLIC_APP_ORIGIN and use it on both server and client, without having to declare another one for server🙂

@ivan-kleshnin
Copy link

Personally, I would just declare NEXT_PUBLIC_APP_ORIGIN and use it on both server and client, without having to declare another one for server🙂

We have 3+ servers in monorepo, including Python based. But, as a general advice, – you're correct and I agree.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants