t3-oss / t3-env Goto Github PK
View Code? Open in Web Editor NEWHome Page: https://env.t3.gg
License: MIT License
Home Page: https://env.t3.gg
License: MIT License
Here is the issue I am seeing,
node_modules/.pnpm/@[email protected]_wnc3eutrhvkpx2b7oumqzgx5he/node_modules/@t3-oss/env-core/dist/index.d.ts:1:8 - error TS1192: Module '"<project folder>/src/sdkMetrics/src/node_modules/.pnpm/[email protected]/node_modules/zod/index"' has no default export.
1 import z, { ZodType, ZodError, ZodObject } from 'zod';
~
node_modules/.pnpm/[email protected]/node_modules/zod/index.d.ts:1:1
1 export * from "./lib";
~~~~~~~~~~~~~~~~~~~~~~
'export *' does not re-export a default.
Found 1 error in node_modules/.pnpm/@[email protected]_wnc3eutrhvkpx2b7oumqzgx5he/node_modules/@t3-oss/env-core/dist/index.d.ts:1
Repro for this: codesandbox-repro
PS: The reproduction is following the same package versions I'm using in the project I'm seeing this error
npm run dev
on terminal - Everything works fine, you can also check by removing from the .env
and you will see that the errors will appear, so it's working as expected.npm run build
on terminal - Errors as shown on the image belowI can say it's a typescript version problem, I'm using 4.6.4
and it doesn't work, but the next stable version I've seen on the typescript changelog was 4.7.2
and it works fine. I don't really know what came new in this version or if it had a bug fix, but I think you should maybe say on Readme the minimum version of typescript required? I don't know if it's enough of proof, but seems like it stops breaking at 4.7.2
I've checked agains these versions of typescript:
Those versions before 4.7.2 seem to be broken.
When I tried to use a server server environment variable in a serever component I get this error
Unhandled Runtime Error
Error: ❌ Attempted to access a server-side environment variable on the client
There should be another step in the docs to add "next.config.mjs" to the include property in tsconfig.
Hi 👋🏻
Been working on a project where we're using Next.js with output: "export"
and t3-env and thought it would be great if we could remove the server
field from createEnv()
altogether or better yet have it error if you try to pass it in at all.
This is more or less the opposite of what's been mentioned in #57 which could be equally useful.
As t3-env has more support for other frameworks, it's possible they too don't have server environment.
I want to add tests for env.mjs
, client side environments can be read, but server side will throw error, how can I mock the server side environment for testing.
env.mjs
import { createEnv } from "@t3-oss/env-nextjs";
import { z } from "zod";
export const env = createEnv({
server: {
NODE_ENV: z.enum(["development", "test", "production"]),
},
client: {
NEXT_PUBLIC_API_BASE_URL: z.string().optional(),
NEXT_PUBLIC_API_TIMEOUT: z.coerce.number().int().positive().optional(),
},
runtimeEnv: {
NODE_ENV: process.env.NODE_ENV,
NEXT_PUBLIC_API_BASE_URL: process.env.NEXT_PUBLIC_API_BASE_URL,
NEXT_PUBLIC_API_TIMEOUT: process.env.NEXT_PUBLIC_API_TIMEOUT,
},
skipValidation: !!process.env.SKIP_ENV_VALIDATION,
});
env.test.ts
import { vi } from "vitest";
const processEnvs = {
NODE_ENV: "test",
NEXT_PUBLIC_API_BASE_URL: "http://localhost:8000",
NEXT_PUBLIC_API_TIMEOUT: "20000",
};
describe("env.mjs", () => {
beforeAll(() => {
Object.entries(processEnvs).forEach(([key, value]) => {
vi.stubEnv(key, value);
});
});
afterAll(() => {
vi.unstubAllEnvs();
});
it("should validate and transform server envs", async () => {
const { env } = await import("./env.mjs");
expect(env.NODE_ENV).toBe("test");
});
it("should validate and transform client envs", async () => {
const { env } = await import("./env.mjs");
expect(env.NEXT_PUBLIC_API_BASE_URL).toBe("http://localhost:8000");
expect(env.NEXT_PUBLIC_API_TIMEOUT).toBe(20_000);
});
});
Error message
FAIL src/env.test.ts > env.mjs > should validate and transform server envs
Error: ❌ Attempted to access a server-side environment variable on the client
❯ onInvalidAccess node_modules/.pnpm/@[email protected][email protected][email protected]/node_modules/@t3-oss/core/index.ts:174:13
❯ Object.get node_modules/.pnpm/@[email protected][email protected][email protected]/node_modules/@t3-oss/core/index.ts:191:16
❯ src/env.test.ts:36:16
34| it("should validate and transform server envs", async () => {
35| const { env } = await import("./env.mjs");
36| expect(env.NODE_ENV).toBe("test");
I think instructions for Astro should include naming the file to a different name, because Astro already has src/env.d.ts
file and something like src/t3Ent.ts
(or t3_env, z_env, safe_env, valid_env, v_env, who knows) would be better. Then again, there's an option of trying to convince Astro to rename their file to astro.d.ts
because I had so many issues with that file being named env :D
p.s. oh, right you need to convince Vite as well :) https://vitejs.dev/guide/env-and-mode.html#intellisense-for-typescript
I think I found a way to leak variables to the client in Astro using Islands with client:only
...
client:only
island, I will get errors and no render if I try to access an server variable, which is expected. But the validation function is in the js island chunk. So some can open dev tools, look at the js chunk and find the variables.Originally posted by @alexanderniebuhr in #60 (comment)
i ran into some weird inference issues when the next.js package extended the core package options type - should be fixed to avoid a bunch of duplications
I'm new to t3-env
, but it's similar to hand-rolled solutions I've used in the past. The "standard" way to define a basic config file seems to be something like the following:
// config.ts
import { createEnv } from "@t3-oss/env-nextjs";
import { z } from "zod";
const config = createEnv({
runtimeEnv: {
DATABASE_URL: process.env.DATABASE_URL,
NEXT_PUBLIC_PASSWORD_MIN_LENGTH:
process.env.NEXT_PUBLIC_PASSWORD_MIN_LENGTH
},
client: {
NEXT_PUBLIC_PASSWORD_MIN_LENGTH: z
.string()
.transform((n) => Number.parseInt(n, 10))
.pipe(z.number().positive())
},
server: { DATABASE_URL: z.string().url() }
});
export default config;
... which could be used like:
// client.ts
import config from "./config";
console.log(config.NEXT_PUBLIC_PASSWORD_MIN_LENGTH);
... or:
// server.ts
import config from "./config";
console.log(config.DATABASE_URL);
My problem with this is 1) my code is now yelling at me everywhere I use env configuration and 2) I constantly have to remember Next's client-safe prefix, even when I use the value on the server. It's forcing me to pollute my code.
What I would prefer is the following:
// config.ts
import { createEnv } from "@t3-oss/env-nextjs";
import { z } from "zod";
const config = createEnv({
runtimeEnv: {
databaseUrl: process.env.DATABASE_URL,
passwordMinLength: process.env.NEXT_PUBLIC_PASSWORD_MIN_LENGTH
},
client: {
passwordMinLength: z
.string()
.transform((n) => Number.parseInt(n, 10))
.pipe(z.number().positive())
},
server: { databaseUrl: z.string().url() }
});
export default config;
This actually seems to work fine for server-only values. I even don't get any type errors when reading the value config.passwordMinLength
. Autocomplete and IDE type inspections also behave as expected. However the client config isn't happy because the client.passwordMinLength
key isn't prefixed with NEXT_PUBLIC_
. This type error cascades into runtimeEnv.passwordMinLength
, which isn't recognized as a valid property name.
[nodemon] restarting due to changes...
[nodemon] starting `ts-node .`
❌ Invalid environment variables: { HTTP_PORT: [ 'Expected number, received string' ] }
import { createEnv } from '@t3-oss/env-core';
import { z } from 'zod';
export const env = createEnv({
clientPrefix: 'PUBLIC_',
client: {},
server: {
NODE_ENV: z.string().regex(/development|production/),
ADMIN_API_KEY: z.string().min(24),
HTTP_PORT: z.number().min(0).max(65535),
SENTRY_DSN: z.string().url(),
},
runtimeEnv: {
NODE_ENV: process.env.NODE_ENV,
ADMIN_API_KEY: process.env.ADMIN_API_KEY,
HTTP_PORT: process.env.HTTP_PORT,
SENTRY_DSN: process.env.SENTRY_DSN,
},
});
Instead of
createEnv({
server: {
KEY: z.string(),
}
});
it should be
createEnv({
server: z.object({
KEY: z.string(),
})
});
and it should not be tied to zod, meaning something like scale-ts could work for example:
import * as $ from 'scale-codec';
createEnv({
server: $.object(
$.field('text', $.str)
),
});
this would require some more considerations for typings as we'd need to pull out the shape of each to get access to the raw keys to compare against the client prefix
Should probably implement #169 first in some half-generic way 🤔
Getting TS2307 error. Following is my TSConfig file:
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"checkJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"noUncheckedIndexedAccess": true
"baseUrl": ".",
"paths": {
"@root/*": [
"src/*"
]
},
"allowJs": true,
"moduleResolution": "node"
},
"include": [
"next.config.mjs",
"next-env.d.ts",
".next/types/**/*.ts",
"**/*.ts",
"**/*.tsx"
],
"exclude": ["node_modules"]
}
Package.json:
{
"dependencies": {
"@acme/core": "0.0.0",
"@acme/db": "0.0.0",
"@prisma/client": "4.15.0",
"@t3-oss/env-nextjs": "^0.4.0",
"@tanstack/react-query": "^4.29.12",
"@trpc/client": "^10.28.2",
"@trpc/next": "^10.28.2",
"@trpc/react-query": "^10.28.2",
"@trpc/server": "10.28.2",
"clsx": "1.2.1",
"daisyui": "3.0.0",
"i18next": "22.5.0",
"next": "13.4.4",
"next-i18next": "13.2.2",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-hook-form": "7.44.2",
"react-i18next": "^12.3.1",
"react-query": "3.39.3",
"superjson": "1.12.3",
"zod": "3.21.4"
},
"devDependencies": {
"@acme/tsconfig": "0.0.0",
"@axe-core/react": "4.7.1",
"@types/node": "20.2.5",
"@types/react": "18.2.7",
"@types/react-dom": "18.2.4",
"autoprefixer": "10.4.14",
"eslint": "8.41.0",
"eslint-config-acme": "0.0.0",
"eslint-plugin-tailwindcss": "3.12.1",
"i18next-typescript": "0.1.0",
"postcss": "8.4.24",
"prettier": "2.8.8",
"prettier-plugin-tailwindcss": "0.3.0",
"tailwindcss": "3.3.2",
"typescript": "5.0.4"
},
}
Unsure if this is a zod issue or a t3-env issue.
Check out latest commit bfa7645
pnpm install
pnpm build
pnpm build --filter=@examples/nextjs
# observe build passes
Change examples/nextjs/tsconfig.json
: set "skipLibCheck": false
.
pnpm build --filter=@examples/nextjs
@examples/nextjs:build: ../../packages/nextjs/dist/index.d.ts:28:371
@examples/nextjs:build: Type error: Type 'k' cannot be used to index type 'addQuestionMarks<{ [k_1 in keyof TServer]: TServer[k_1]["_output"]; }>'.
@examples/nextjs:build:
@examples/nextjs:build: 26 | runtimeEnv: StrictOptions<ClientPrefix, TServer, TClient>["runtimeEnvStrict"];
@examples/nextjs:build: 27 | }
@examples/nextjs:build: > 28 | declare function createEnv<TServer extends Record<string, ZodType> = NonNullable<unknown>, TClient extends Record<`${ClientPrefix}${string}`, ZodType> = NonNullable<unknown>>({ runtimeEnv, ...opts }: Options<TServer, TClient>): (zod.objectUtil.addQuestionMarks<{ [k_1 in keyof TServer]: TServer[k_1]["_output"]; }> extends infer T_3 extends object ? { [k in keyof T_3]: zod.objectUtil.addQuestionMarks<{ [k_1 in keyof TServer]: TServer[k_1]["_output"]; }>[k]; } : never) & (zod.objectUtil.addQuestionMarks<{ [k_2 in keyof TClient]: TClient[k_2]["_output"]; }> extends infer T_4 extends object ? { [k_1 in keyof T_4]: zod.objectUtil.addQuestionMarks<{ [k_2 in keyof TClient]: TClient[k_2]["_output"]; }>[k_1]; } : never) extends infer T ? { [P in keyof T]: ((zod.objectUtil.addQuestionMarks<{ [k_1 in keyof TServer]: TServer[k_1]["_output"]; }> extends infer T_1 extends object ? { [k in keyof T_1]: zod.objectUtil.addQuestionMarks<{ [k_1 in keyof TServer]: TServer[k_1]["_output"]; }>[k]; } : never) & (zod.objectUtil.addQuestionMarks<{ [k_2 in keyof TClient]: TClient[k_2]["_output"]; }> extends infer T_2 extends object ? { [k_1 in keyof T_2]: zod.objectUtil.addQuestionMarks<{ [k_2 in keyof TClient]: TClient[k_2]["_output"]; }>[k_1]; } : never))[P]; } : never;
@examples/nextjs:build: | ^
@examples/nextjs:build: 29 |
@examples/nextjs:build: 30 | export { createEnv };
@examples/nextjs:build: 31 |
@examples/nextjs:build: ELIFECYCLE Command failed with exit code 1.
probably a good idea to add some tests 🤷🏼
some type tests to make sure inference is okay, that typeerrors are thrown for invalid client variables (prefix not matching), runtimeEnvStrict should require all keys
some runtime tests for different environements (server/edge/client) and make sure errors are thrown when they should
when using this package with nuxt
to type VITE_
env vars, i am running into the following issue: nuxt
types import.meta.env
as Record<string, string | boolean | undefined>
(see here) - i am guessing because vite
sets these default values. however, t3-env
enforces runtimeEnvStrict
to be Record<string, string | undefined>
. would you consider allowing Record<string, string | boolean | undefined>
on runtimeEnvStrict
to cover this usecase?
Here are my configs
// source: /src/env.mjs
import { createEnv } from "@t3-oss/env-core";
import { z } from "zod";
export const EnvironmentVariables = createEnv({
clientPrefix: "NEXT_PUBLIC_",
server: {},
client: {
NEXT_PUBLIC_API_REST_URL: z.string().url(),
NEXT_PUBLIC_API_GRAPGQL_URL: z.string().url(),
NEXT_PUBLIC_BOOKING_PAGE_URL: z.string().url(),
},
runtimeEnv: process.env, // or `import.meta.env`, or similar
});
With NextJS (probably same for most of the framework nowadays), only envs with PREFIX_
can be accessed in client-side, however, NODE_ENV
is technically accessible in browser too.
So, the question is, should the library expose the NODE_ENV
to the client-side?
One of the use-cases that I used personally is to show the Tailwind screen size indicator in NODE_ENV === 'development'
. As of now, I can work around the limitation by creating a NEXT_PUBLIC_NODE_ENV: process.env.NODE_ENV
in runtimeEnv
, but it feels weird.
I wonder if it is possible to load the environment variables at runtime, as with "https://github.com/expatfile/next-runtime-env". This is not possible in the example with nextjs.
If i understand it right, the package assumes both the environment variables are mutually exclusive? What if I wish to use a variable in both the client and server side. For example, NEXT_PUBLIC_GOOGLE_OAUTH2_CLIENT_ID
.
Currently, redefining the variable seems to be the only way to go about. Is it possible to have a shared
set of values?
next.config.js
requires the environment file to be in js. However, the runtimeEnv
checking doesn't work on js files.
As listed in the docs here: https://env.t3.gg/docs/nextjs
When using t3-env on a container scenario, it will be useful to force a process.exit
when invalid variables are detected.
This is due to container environments usually relying on different containers state reports to check wether it is working properly or it should be rolled back.
This can probably be implemented as a optional flag defaulted to false like exitOnFailureValidation
.
The current behaviour is simply a throw via console.
❯ docker run -p 3000:3000 t3-test-app
Listening on port 3000 url: http://e1699a2062d6:3000
❌ Invalid environment variables: {
DATABASE_URL: [ 'Required' ],
NEXTAUTH_SECRET: [ 'Required' ],
NEXTAUTH_URL: [ 'Required' ]
}
Error: Invalid environment variables
at f (file:///app/node_modules/.pnpm/@[email protected][email protected][email protected]/node_modules/@t3-oss/env-nextjs/dist/index.mjs:1:386)
at l (file:///app/node_modules/.pnpm/@[email protected][email protected][email protected]/node_modules/@t3-oss/env-nextjs/dist/index.mjs:1:576)
at C (file:///app/node_modules/.pnpm/@[email protected][email protected][email protected]/node_modules/@t3-oss/env-nextjs/dist/index.mjs:1:749)
at /app/.next/server/chunks/115.js:98:74
Error: Invalid environment variables
at f (file:///app/node_modules/.pnpm/@[email protected][email protected][email protected]/node_modules/@t3-oss/env-nextjs/dist/index.mjs:1:386)
at l (file:///app/node_modules/.pnpm/@[email protected][email protected][email protected]/node_modules/@t3-oss/env-nextjs/dist/index.mjs:1:576)
at C (file:///app/node_modules/.pnpm/@[email protected][email protected][email protected]/node_modules/@t3-oss/env-nextjs/dist/index.mjs:1:749)
at /app/.next/server/chunks/115.js:98:74
Error: Invalid environment variables
at f (file:///app/node_modules/.pnpm/@[email protected][email protected][email protected]/node_modules/@t3-oss/env-nextjs/dist/index.mjs:1:386)
at l (file:///app/node_modules/.pnpm/@[email protected][email protected][email protected]/node_modules/@t3-oss/env-nextjs/dist/index.mjs:1:576)
at C (file:///app/node_modules/.pnpm/@[email protected][email protected][email protected]/node_modules/@t3-oss/env-nextjs/dist/index.mjs:1:749)
at /app/.next/server/chunks/115.js:98:74
Error: Invalid environment variables
at f (file:///app/node_modules/.pnpm/@[email protected][email protected][email protected]/node_modules/@t3-oss/env-nextjs/dist/index.mjs:1:386)
at l (file:///app/node_modules/.pnpm/@[email protected][email protected][email protected]/node_modules/@t3-oss/env-nextjs/dist/index.mjs:1:576)
at C (file:///app/node_modules/.pnpm/@[email protected][email protected][email protected]/node_modules/@t3-oss/env-nextjs/dist/index.mjs:1:749)
at /app/.next/server/chunks/115.js:98:74
Error: Invalid environment variables
at f (file:///app/node_modules/.pnpm/@[email protected][email protected][email protected]/node_modules/@t3-oss/env-nextjs/dist/index.mjs:1:386)
at l (file:///app/node_modules/.pnpm/@[email protected][email protected][email protected]/node_modules/@t3-oss/env-nextjs/dist/index.mjs:1:576)
at C (file:///app/node_modules/.pnpm/@[email protected][email protected][email protected]/node_modules/@t3-oss/env-nextjs/dist/index.mjs:1:749)
at /app/.next/server/chunks/115.js:98:74
Error: Invalid environment variables
at f (file:///app/node_modules/.pnpm/@[email protected][email protected][email protected]/node_modules/@t3-oss/env-nextjs/dist/index.mjs:1:386)
at l (file:///app/node_modules/.pnpm/@[email protected][email protected][email protected]/node_modules/@t3-oss/env-nextjs/dist/index.mjs:1:576)
at C (file:///app/node_modules/.pnpm/@[email protected][email protected][email protected]/node_modules/@t3-oss/env-nextjs/dist/index.mjs:1:749)
at /app/.next/server/chunks/115.js:98:74```
Would be nice if we're just using this for the server if we didn't need to pass in client
and clientPrefix
.
import { createEnv } from "@t3-oss/env-core";
import { z } from "zod";
export const env = createEnv({
server: {
DATABASE_URL: z.string().url(),
OPEN_AI_API_KEY: z.string().min(1),
},
client: {}, // this is needed even if it's not used
clientPrefix: 'client_', // this is needed even if it's not used
runtimeEnv: process.env,
});
I would like to use booleans without as unknown as string
.
NEXT_PUBLIC_DEBUG: isDebug() as unknown as string,
import { createEnv } from "@t3-oss/env-nextjs";
import { z } from "zod";
import {zCustomerNames} from "#/customers/customers";
export const env = createEnv({
/*
* Serverside Environment variables, not available on the client.
* Will throw if you access these variables on the client.
*/
server: {
UPSTASH_REDIS_REST_URL: z.string().url().startsWith("https://"),
UPSTASH_REDIS_REST_TOKEN: z.string().length(100),
REACT_EDITOR: z.string().min(1),
},
/*
* Environment variables available on the client (and server).
*
* 💡 You'll get typeerrors if these are not prefixed with NEXT_PUBLIC_.
*/
client: {
NEXT_PUBLIC_GOOGLE_MAPS_API_KEY: z.string().length(39),
NEXT_PUBLIC_GOOGLE_MAPS_PLACE_ID: z.string().length(27),
NEXT_PUBLIC_DEBUG: z.boolean(),
NEXT_PUBLIC_URL: z.string().url().startsWith("https://"),
NEXT_PUBLIC_CUSTOMER: zCustomerNames,
},
/*
* Due to how Next.js bundles environment variables on Edge and Client,
* we need to manually destructure them to make sure all are included in bundle.
*
* 💡 You'll get typeerrors if not all variables from `server` & `client` are included here.
*/
runtimeEnv: {
UPSTASH_REDIS_REST_URL: process.env.UPSTASH_REDIS_REST_URL,
UPSTASH_REDIS_REST_TOKEN: process.env.UPSTASH_REDIS_REST_TOKEN,
REACT_EDITOR: process.env.REACT_EDITOR,
NEXT_PUBLIC_GOOGLE_MAPS_API_KEY: process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY,
NEXT_PUBLIC_GOOGLE_MAPS_PLACE_ID: process.env.NEXT_PUBLIC_GOOGLE_MAPS_PLACE_ID,
NEXT_PUBLIC_DEBUG: isDebug() as unknown as string,
NEXT_PUBLIC_URL: process.env.NEXT_PUBLIC_URL,
NEXT_PUBLIC_CUSTOMER: process.env.NEXT_PUBLIC_CUSTOMER,
},
});
function isDebug() {
return (process.env.NEXT_PUBLIC_DEBUG === "true" || process.env.NEXT_PUBLIC_DEBUG === "1")
}
You can accidentally include one of your public environment variables in the server
object, preventing you from accessing it on the client even though it is public. There should probably be an error if the server variable section has a public prefix, to prevent this from happening.
For example, this env
in NextJS:
export const env = createEnv({
server: {
DATABASE_URL: z.string().url(),
NEXT_PUBLIC_API_KEY: z.string().min(1),
// ^^^^^^^^^^^^ ideally, this should be an error
},
client: {
NEXT_PUBLIC_OTHER_VARIABLE: z.string().url(),
},
runtimeEnv: { ... },
});
Not sure how big of an issue this is but it seems like a good and fairly easy fix. I can send a PR if it would be helpful.
Currently, docs are using next/mdx
which makes the file structure complicated with docs/slug/page.mdx
. I don't think this is the best way to organize our documentation.
To fix this, I suggest we use Contentlayer instead. This will give us a lot of benefits, like:
Currently, we are limited to using the same head for each document, which is not ideal for SEO. With Contentlayer, we can have a head for each document, which will improve SEO.
I believe this change will be very helpful for the project. If the maintainers agree, I am willing to start working on it. Thank you for considering my proposal.
was converting my env usage from the built in nuxt one (using useRuntimeConfig) to the t3-env
ran into a a situation that is not supported
// serve/utils/kysely.ts
import { Kysely } from 'kysely'
import { PlanetScaleDialect } from 'kysely-planetscale'
import type { DB } from '~/lib/db'
import { env } from '~/env'
export const db = new Kysely<DB>({
dialect: new PlanetScaleDialect({
url: env.DATABASE_URL,
}),
})
this is something that does work when using Nuxt's default runtime as such
import { Kysely } from 'kysely'
import { PlanetScaleDialect } from 'kysely-planetscale'
import type { DB } from '~/lib/db'
const config = useRuntimeConfig()
export const db = new Kysely<DB>({
dialect: new PlanetScaleDialect({
url: config.DATABASE_URL,
}),
})
getting an error that crashes the edge function saying DATABASE_URL is required
this only happens when deploying to vercel-edge as opposed to just the vercel preset.
do environment variables load differently when bundled for edge? is there a way to make it use nuxt's own runtimeConfig https://nuxt.com/docs/api/composables/use-runtime-config#environment-variables
t3-env
is great for managing environment variables.
However, there's a gap when it comes to command-line operations like drizzle-kit db:push
.
Currently, we use dotenv-cli
to load environment variables:
$ dotenv -- drizzle-kit push:mysql
This method lacks a safety check for the loaded variables.
If we created a CLI for t3-env
, it could safely load, transform, and verify these variables:
$ t3-env -- drizzle-kit push:mysql
# Loads and transforms environment variables
# Throw errors if there's an issue
# If no issues, execute the command
See more: https://vercel.com/changelog/more-flexible-environment-variables-in-edge-functions-and-middleware
We could change the way, the library works with this. I am open to create a PR.
As it's currently implemented, both server and client code refer to the same instance of t3-env (the output of createEnv()
)
This means that using T3-env leaks the following information about your server-side environment variables in your client-side code:
While this is nowhere near as bad as the values themselves being leaked, I think that is still some cause for concern.
I think there is at least some potential for consumers of this library to unknowingly leak secret sever-side implementation details, not realizing that server variable names and zod schemas will be visible in their client code.
I wonder if there's a way to split up the client and server envs so that the server code can be tree-shaken out of the client bundle? If it's even possible, it would probably be a drastic change to the API though.
If the API cannot be changed to avoid leaking server info to the client, it would probably make sense to add a note in the documentation warning users that this information will end up in their client JS.
build time variables are accessible to both server and client, but is not prefixed with the client prefix, e.g. NODE_ENV
, VERCEL_URL
.
Should we add a new section for those?
const env = createEnv({
build: {
NODE_ENV: z.enum(...),
VERCEL_URL: z.preprocess(/* do something to validate if this should exist or not */),
},
server: { ... },
// ...
});
Currently, having NODE_ENV under server is the only way supported in t3-env, but the variable is actually defined on the client too, so we have to escape and use process.env.NODE_ENV throughout the codebase
In the documentation there is a section called Validate schema on build (recommended). I'm wondering why it should be recommended to do it at build time?
The reason to read environment variables at runtime (or when the server process starts) is to avoid doing a build for every environment. For commertial projects it is common to have multiple environments where an app is deployed to. It is quicker and more robust to build only once and use the resulting build artifact for all deployments to those environments.
How are you solving such situations?
Thank you for creating this library!
Lots of testing frameworks (Vitest, Cypress) require a prefix when running tests for env vars. When the validation fails in a test env, the error message should be different suggesting that the user should look into if their testing framework requires a prefix for env vars to be loaded
At the moment properties clientPrefix
and client
are required in createEnv
options.
These options are unnecessary for API only apps (express).
For the env-core
package it would be great to make these options optional.
> Same type error happening again, probably something wrong with the CI.
Yea might be a bad turbo config so it's pulling a bad cache or something... i'll take a look! let's get this released :)
Originally posted by @juliusmarminge in #21 (comment)
I can't get build-time validation working on a vitejs app. I want to run the schema validation on build time so I can check if all required environment variables are defined or not.
npm create vite@latest
npm install @t3-oss/env-core zod
VITE_TEST_VAR="ABC"
VITE_MISSING="123"
@t3-oss/env-core
required env.ts
file with the following contentimport { createEnv } from "@t3-oss/env-core";
import { z } from "zod";
export const env = createEnv({
/*
* Specify what prefix the client-side variables must have.
* This is enforced both on type-level and at runtime.
*/
clientPrefix: "VITE_",
client: {
VITE_TEST_VAR: z.string(),
VITE_MISSING: z.string(), // I used this variable to test the build time validation works
},
/**
* What object holds the environment variables at runtime.
* Often `process.env` or `import.meta.env`
*/
runtimeEnv: import.meta.env,
});
App.tsx
(so we verify that env vars are actually usable within the client app)// App.tsx
import "./App.css";
import { env } from "./env.ts";
function App() {
return (
<>
<h1>{env.VITE_TEST_VAR}</h1>
</>
);
}
export default App;
❌ Invalid environment variables: { VITE_TEST_VAR: [ 'Required' ], VITE_MISSING: [ 'Required' ] }
failed to load config from /Users/user/dev/project/vite.config.ts
error during build:
Error: Invalid environment variables
at f (file:///Users/user/dev/project/node_modules/@t3-oss/env-core/dist/index.mjs:1:426)
at g (file:///Users/user/dev/project/node_modules/@t3-oss/env-core/dist/index.mjs:1:616)
at file:///Users/user/dev/project/vite.config.ts.timestamp-1686946000230-87681b7d7ccd5.mjs:8:11
at ModuleJob.run (node:internal/modules/esm/module_job:192:25)
error Command failed with exit code 1.
As Next.js sets NODE_ENV
to production
when running next lint
, the following example fails if the NEXTAUTH_SECRET
isn't present in the local env file:
NEXTAUTH_SECRET:
process.env.NODE_ENV === "production"
? z.string().min(1)
: z.string().min(1).optional(),
One option could be changing the "lint": "next lint"
to "lint": "NODE_ENV=development next lint"
and that works when linting the whole project, but likely will have issues using husky and lint-staged.
Is there any recommended solution? Or should the validations be skipped completely when running next lint
?
Once it got to my CI, the validation failed -- and whats weird is it ran successfully locally. Would be nice if it failed nicely in a CI environment, but also my test shouldn't have accessed a env variable.
$ npm test
> /[email protected] test
> mocha --require ts-node/register --timeout 100000 --require ./dist/test-setup.js './src/**/*.spec.js' './src/**/*.spec.ts'
❌ Invalid environment variables: {
...
}
Error: Invalid environment variables
at /project/node_modules/@t3-oss/env-core/index.ts:155:1
at g (/project/node_modules/@t3-oss/env-core/index.ts:155:1)
at Object.<anonymous> (/project/src/env.ts:5:29)
at Module._compile (node:internal/modules/cjs/loader:1097:14)
at Module.m._compile (/project/node_modules/ts-node/src/index.ts:1618:23)
at Module._extensions..js (node:internal/modules/cjs/loader:1149:10)
at Object.require.extensions.<computed> [as .ts] (/project/node_modules/ts-node/src/index.ts:1621:12)
at Module.load (node:internal/modules/cjs/loader:975:32)
at Function.Module._load (node:internal/modules/cjs/loader:822:12)
at Module.require (node:internal/modules/cjs/loader:999:19)
at require (node:internal/modules/cjs/helpers:102:18)
at Object.<anonymous> (/project/src/victorops/api.ts:14:1)
at Module._compile (node:internal/modules/cjs/loader:1097:14)
at Module.m._compile (/project/node_modules/ts-node/src/index.ts:1618:23)
at Module._extensions..js (node:internal/modules/cjs/loader:1149:10)
at Object.require.extensions.<computed> [as .ts] (/project/node_modules/ts-node/src/index.ts:1621:12)
at Module.load (node:internal/modules/cjs/loader:975:32)
at Function.Module._load (node:internal/modules/cjs/loader:822:12)
at Module.require (node:internal/modules/cjs/loader:999:19)
at require (node:internal/modules/cjs/helpers:102:18)
at Object.<anonymous> (/project/src/data/Team.ts:3:1)
at Module._compile (node:internal/modules/cjs/loader:1097:14)
at Module.m._compile (/project/node_modules/ts-node/src/index.ts:1618:23)
at Module._extensions..js (node:internal/modules/cjs/loader:1149:10)
at Object.require.extensions.<computed> [as .ts] (/project/node_modules/ts-node/src/index.ts:1621:12)
at Module.load (node:internal/modules/cjs/loader:975:32)
at Function.Module._load (node:internal/modules/cjs/loader:822:12)
at Module.require (node:internal/modules/cjs/loader:999:19)
at require (node:internal/modules/cjs/helpers:102:18)
at Object.<anonymous> (/project/src/consts.js:2:1)
at Module._compile (node:internal/modules/cjs/loader:1097:14)
at Module.m._compile (/project/node_modules/ts-node/src/index.ts:1618:23)
at Module._extensions..js (node:internal/modules/cjs/loader:1149:10)
at Object.require.extensions.<computed> [as .js] (/project/node_modules/ts-node/src/index.ts:1621:12)
at Module.load (node:internal/modules/cjs/loader:975:32)
at Function.Module._load (node:internal/modules/cjs/loader:822:12)
at Module.require (node:internal/modules/cjs/loader:999:19)
at require (node:internal/modules/cjs/helpers:102:18)
at Object.<anonymous> (/project/src/puppet/consts.js:39:6)
at Module._compile (node:internal/modules/cjs/loader:1097:14)
at Module.m._compile (/project/node_modules/ts-node/src/index.ts:1618:23)
at Module._extensions..js (node:internal/modules/cjs/loader:1149:10)
at Object.require.extensions.<computed> [as .js] (/project/node_modules/ts-node/src/index.ts:1621:12)
at Module.load (node:internal/modules/cjs/loader:975:32)
at Function.Module._load (node:internal/modules/cjs/loader:822:12)
at Module.require (node:internal/modules/cjs/loader:999:19)
at require (node:internal/modules/cjs/helpers:102:18)
at Object.<anonymous> (/project/src/puppet/test/controller.spec.js:2:16)
at Module._compile (node:internal/modules/cjs/loader:1097:14)
at Module.m._compile (/project/node_modules/ts-node/src/index.ts:1618:23)
at Module._extensions..js (node:internal/modules/cjs/loader:1149:10)
at Object.require.extensions.<computed> [as .js] (/project/node_modules/ts-node/src/index.ts:1621:12)
at Module.load (node:internal/modules/cjs/loader:975:32)
at Function.Module._load (node:internal/modules/cjs/loader:822:12)
at ModuleWrap.<anonymous> (node:internal/modules/esm/translators:168:29)
at ModuleJob.run (node:internal/modules/esm/module_job:195:25)
at async Promise.all (index 0)
at async ESMLoader.import (node:internal/modules/esm/loader:337:24)
at async importModuleDynamicallyWrapper (node:internal/vm/module:437:15)
at async formattedImport (/project/node_modules/mocha/lib/nodejs/esm-utils.js:9:14)
at async Object.exports.requireOrImport (/project/node_modules/mocha/lib/nodejs/esm-utils.js:42:28)
at async Object.exports.loadFilesAsync (/project/node_modules/mocha/lib/nodejs/esm-utils.js:100:20)
at async singleRun (/project/node_modules/mocha/lib/cli/run-helpers.js:125:3)
at async Object.exports.handler (/project/node_modules/mocha/lib/cli/run.js:370:5)
Anyways, for now I did a
export const env = process.env.IS_CI
? process.env
: createEnv({});
Thoughts?
I'm trying to make this library work for Nuxt 3 application, but encountering errors:
The process variable is used here: https://github.com/t3-oss/t3-env/blob/b68140ae970c2626d56ed85b10be24dc909f8cab/packages/nuxt/index.ts#L22C66
The error originates from here:
Line 123 in b68140a
I would like to test with Vitest and NextJS, unfortunately there are some errors:
Error: Invalid environment variables
❯ onValidationError node_modules/@t3-oss/core/index.ts:184:13
❯ h node_modules/@t3-oss/core/index.ts:196:12
❯ Module.P node_modules/@t3-oss/env-nextjs/index.ts:73:10
❯ env.mjs:4:20
2| import { z } from "zod";
3|
4| export const env = createEnv(
As far as I understand I need to ovverride the default VITE_ env prefix somewhere in the vitest.config.js.
export default defineConfig({
plugins: [react()],
test: {
setupFiles: '@t3-oss/env-nextjs', // here
},
Could somebody help me how to set properly the setupFiles row with t3-env?
This would be very useful for being able to load e.g. a seperate database url during testing
Note: The following is kind of like a recipe page awaiting its debut in the t3-env docs. Please consider this a proposal before I (or someone else) do so.
When Astro is built with the default Static Site Generation (SSG), the validation will happen on each static route generation at the place of env
usage. Technically this is "Validation on build".
However, with Astro Server-side Rendering (SSR), validation takes a different "route" (no pun intended). If we just switch from SSG to SSR with npm astro add node
for example, immediately we will see that no validation is run when running npm run build
. Let's force it by importing the validation file in astro.config.ts
:
import "./src/env-validate";
// ... or if delaying the validation for later on in the file:
await import("./src/env-validate");
Now npm run build
will tell us that our environment variables are nowhere to be found in import.meta.env
. How comes?
With Astro SSR, you have 1 chance at running the validation at build time, which is the moment when astro.config.ts
is evaluated at the beginning of the build. Unfortunately import.meta.env
at this "config reading time" is not populated yet, so you have to fallback to process.env
or reading the .env
files manually with Vite's loadEnv
function. Thus putting import.meta.env
into runtimeEnv
doesn't work anymore with Astro SSR.
When working with Astro SSR, because there are the build-time and the runtime, there exists different types of environment variables. Here is one way to do it in Astro SSR, Node.js runtime. Note that each runtime has different ways of configuring t3-env, I'm just providing one of the Node runtime solutions.
// astro.config.ts
// ...
// Formulate the BUILDVERSION env var if not available
if (!process.env.BUILDVERSION) {
console.log("Generating build version...");
const lastCommitHash = execSync("git rev-parse --short HEAD")
.toString()
.slice(0, -1);
const lastCommitTime = Math.floor(
Number(
new Date(execSync("git log -1 --format=%cd ").toString()).getTime() /
60000
)
);
process.env.BUILDVERSION = `${lastCommitTime}/${lastCommitHash}`;
}
// Assign .env to the fileEnv object. See fileEnv.ts for more details.
Object.assign(fileEnv, loadEnv(import.meta.env.MODE, process.cwd(), ""));
// Validate the environment variables
await import("./src/t3-env");
export default defineConfig({
// ...
output: "server",
adapter: node({
// ...
}),
vite: {
define: {
"process.env.BUILDVERSION": JSON.stringify(process.env.BUILDVERSION),
},
},
});
// fileEnv.ts
// (Server-side only) This file contains an exported object that will be
// assigned the content of the .env files (if any) by the astro.config.mjs file.
//
// The purpose is to use variables in the .env file in the
// ./src/env-validate.ts file without that file having to use Vite's `loadEnv`
// function, causing the `fsevents` module to be bundled in the client-side
// bundle.
const fileEnv: Record<string, string> = {};
export default fileEnv;
// t3-env.ts
import { createEnv } from "@t3-oss/env-core";
import { z } from "zod";
import fileEnv from "./fileEnv";
const isServer = typeof window === "undefined";
const isBuilding = isServer && process.env.BUILDING === "true";
export const serverValidation = {
build: {
// Put in here any server-side variable that is hardcoded on build, i.e.
// defined with Vite config's `define` option.
BUILDVERSION: z.string().nonempty(),
},
runtime: {
// Put in here any server-side variable that is read at runtime, i.e. being
// read from `process.env`.
NODE_ENV: z.enum(["development", "test", "production"]),
AUTOCOMPLETE_HOST: z.string().nonempty(),
API_HOST: z.string().nonempty(),
APP_DOMAIN: z.string().nonempty(),
},
};
export const env = createEnv({
server: (isBuilding
? serverValidation.build
: {
...serverValidation.build,
...serverValidation.runtime,
}) as typeof serverValidation.build & typeof serverValidation.runtime,
clientPrefix: "PUBLIC_",
// The client variables are hardcoded at build time and can only be read from
// `import\u002meta.env`. No `process.env` available on client - there is no
// runtime client variable.
client: {
PUBLIC_EXAMPLE_HARDCODED_CLIENT_VAR: z.string().nonempty(),
},
runtimeEnv: {
...fileEnv, // For development, loading from .env and the environment
// After build, import\u002meta.env contains:
// - Client variables
// - (Only on the server version of the bundle) Any env var that is present
// in the code and is indeed present in the environment (at the build
// time). If it is used in the code but doesn't exist in the environment,
// it won't be included here. For example, if the environment has a
// variable named `VARIABLE` and in the code there is an exact match of
// `VARIABLE` (even `abcVARIABLEabc`), then this object will be like `{
// VARIABLE: process.env.VARIABLE }`.
//
// Example after build:
// ```
// // Server version (by Astro's vite-plugin-env)
// Object.assign({ PUBLIC_VAR: "abc"}, { BUILDING: process.env.BUILDING })`
//
// // Client version (by Vite)
// { PUBLIC_VAR: "abc" }
// ```
//
// See https://github.com/withastro/astro/blob/main/packages/astro/src/vite-plugin-env/index.ts
//
// The purpose is to continue fetching these vars from the environment at
// runtime.
//
// Note that if the variables are:
// - In the .env file (instead of in the environment), or
// - Defined in the Vite's `define` option with `process.env.XXX`
// then they will be included in the bundle at build time. The reason for
// the former is... they are defined that way by Astro's vite-plugin-env.
// The reason for the latter is, after it becomes `process.env.XXX`, Vite's
// `define` will go and replace that with something else.
//
// Example: Server bundle after build (note the `BUILDVERSION`):
// ```
// Object.assign({ PUBLIC_VAR: "abc"}, { BUILDVERSION: "asdasd", BUILDING: process.env.BUILDING })`.
// ```
//
// As for our use case, we use this object for client variables and
// build-time server-side variables (by defining `process.env.XXX` via
// Vite's `define`).
...import.meta.env,
// The above object just provides client-side variables and build-time
// server-side variables. The below is the runtime environment, providing
// runtime server-side variables.
//
// We don't want the client to touch `process`, hence the `isServer`.
...(isServer ? process.env : {}),
},
});
Does this library work only with .env
?
I ask because Next.js has support for different types of .env files:
Particularly I have tried with .env.test
but it does not work for me.
This hasn't been released yet, but at a glance it appears as if Next is dropping the need to destructure process.env.
I'm facing the issue where it completely breaks if any of the env variables have $% combination of symbols.
In case where it is present, all other enviroment variables became undefined too.
Am I doing something wrong, or it's just a bug?
Thanks.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.