Skip to content

Commit

Permalink
feat: make runtimeEnv optional in Next.js and default to `process.e…
Browse files Browse the repository at this point in the history
…nv` (#75)
  • Loading branch information
juliusmarminge committed Jun 25, 2023
1 parent 41084fd commit 8568a9b
Show file tree
Hide file tree
Showing 9 changed files with 141 additions and 60 deletions.
7 changes: 7 additions & 0 deletions .changeset/curly-beers-eat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@t3-oss/env-nextjs": minor
---

introduce a new `experimental__runtimeEnv` that only requires manual destruction of client side variables.

next.js 13.4.4 dropped the static analysis of serverside environment variables, which means that the manual destruction is no longer necessary for serverside variables
2 changes: 1 addition & 1 deletion docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"class-variance-authority": "^0.4.0",
"clsx": "^1.2.1",
"lucide-react": "0.176.0",
"next": "^13.4.1",
"next": "^13.4.4",
"next-themes": "^0.2.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
Expand Down
5 changes: 5 additions & 0 deletions docs/src/app/docs/nextjs/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,16 @@ export const env = createEnv({
client: {
NEXT_PUBLIC_PUBLISHABLE_KEY: z.string().min(1),
},
// If you're using Next.js < 13.4.4, you'll need to specify the runtimeEnv manually
runtimeEnv: {
DATABASE_URL: process.env.DATABASE_URL,
OPEN_AI_API_KEY: process.env.OPEN_AI_API_KEY,
NEXT_PUBLIC_PUBLISHABLE_KEY: process.env.NEXT_PUBLIC_PUBLISHABLE_KEY,
},
// For Next.js >= 13.4.4, you only need to destructure client variables:
// experimental__runtimeEnv: {
// NEXT_PUBLIC_PUBLISHABLE_KEY: process.env.NEXT_PUBLIC_PUBLISHABLE_KEY,
// }
});
```

Expand Down
4 changes: 2 additions & 2 deletions examples/nextjs/app/env.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ export const env = createEnv({
client: {
NEXT_PUBLIC_GREETING: z.string(),
},
runtimeEnv: {
SECRET: process.env.SECRET,

experimental__runtimeEnv: {
NEXT_PUBLIC_GREETING: process.env.NEXT_PUBLIC_GREETING,
},
});
2 changes: 1 addition & 1 deletion examples/nextjs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"@types/react": "18.0.38",
"@types/react-dom": "18.0.11",
"eslint": "^8.39.0",
"next": "^13.4.1",
"next": "^13.4.4",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"typescript": "^5.0.4",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@t3-oss/env-root",
"packageManager": "pnpm@8.3.1",
"packageManager": "pnpm@8.6.3",
"private": true,
"type": "module",
"scripts": {
Expand Down
56 changes: 43 additions & 13 deletions packages/nextjs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,35 +9,65 @@ import {
const CLIENT_PREFIX = "NEXT_PUBLIC_" as const;
type ClientPrefix = typeof CLIENT_PREFIX;

interface Options<
type Options<
TServer extends Record<string, ZodType>,
TClient extends Record<`${ClientPrefix}${string}`, ZodType>
> extends Omit<
StrictOptions<ClientPrefix, TServer, TClient> &
ServerClientOptions<ClientPrefix, TServer, TClient>,
"runtimeEnvStrict" | "runtimeEnv" | "clientPrefix"
> {
/**
* Manual destruction of `process.env`. Required for Next.js.
*/
runtimeEnv: StrictOptions<ClientPrefix, TServer, TClient>["runtimeEnvStrict"];
}
> = Omit<
StrictOptions<ClientPrefix, TServer, TClient> &
ServerClientOptions<ClientPrefix, TServer, TClient>,
"runtimeEnvStrict" | "runtimeEnv" | "clientPrefix"
> &
(
| {
/**
* Manual destruction of `process.env`. Required for Next.js < 13.4.4.
*/
runtimeEnv: StrictOptions<
ClientPrefix,
TServer,
TClient
>["runtimeEnvStrict"];
experimental__runtimeEnv?: never;
}
| {
runtimeEnv?: never;
/**
* Can be used for Next.js ^13.4.4 since they stopped static analysis of server side `process.env`.
* Only client side `process.env` is statically analyzed and needs to be manually destructured.
*/
experimental__runtimeEnv: Record<
{
[TKey in keyof TClient]: TKey extends `${ClientPrefix}${string}`
? TKey
: never;
}[keyof TClient],
string | boolean | number | undefined
>;
}
);

export function createEnv<
TServer extends Record<string, ZodType> = NonNullable<unknown>,
TClient extends Record<
`${ClientPrefix}${string}`,
ZodType
> = NonNullable<unknown>
>({ runtimeEnv, ...opts }: Options<TServer, TClient>) {
>(opts: Options<TServer, TClient>) {
const client = typeof opts.client === "object" ? opts.client : {};
const server = typeof opts.server === "object" ? opts.server : {};

const runtimeEnv = opts.runtimeEnv
? opts.runtimeEnv
: {
...process.env,
...opts.experimental__runtimeEnv,
};

return createEnvCore<ClientPrefix, TServer, TClient>({
...opts,
client,
server,
clientPrefix: CLIENT_PREFIX,
runtimeEnvStrict: runtimeEnv,
runtimeEnv,
});
}
42 changes: 42 additions & 0 deletions packages/nextjs/test/smoke.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,48 @@ test("runtimeEnv enforces all keys", () => {
});
});

test("new experimental runtime option only requires client vars", () => {
ignoreErrors(() => {
createEnv({
server: { BAR: z.string() },
client: { NEXT_PUBLIC_BAR: z.string() },
// @ts-expect-error - NEXT_PUBLIC_BAR is missing
experimental__runtimeEnv: {},
});
createEnv({
server: { BAR: z.string() },
client: { NEXT_PUBLIC_BAR: z.string() },
experimental__runtimeEnv: {
// @ts-expect-error - BAR should not be specified
BAR: "bar",
},
});
});

process.env = {
BAR: "bar",
NEXT_PUBLIC_BAR: "foo",
};

const env = createEnv({
server: { BAR: z.string() },
client: { NEXT_PUBLIC_BAR: z.string() },
experimental__runtimeEnv: {
NEXT_PUBLIC_BAR: process.env.NEXT_PUBLIC_BAR,
},
});

expectTypeOf(env).toEqualTypeOf<{
BAR: string;
NEXT_PUBLIC_BAR: string;
}>();

expect(env).toMatchObject({
BAR: "bar",
NEXT_PUBLIC_BAR: "foo",
});
});

describe("return type is correctly inferred", () => {
test("simple", () => {
const env = createEnv({
Expand Down
81 changes: 39 additions & 42 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 comment on commit 8568a9b

@vercel
Copy link

@vercel vercel bot commented on 8568a9b Jun 25, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

t3-env – ./

t3-env-git-main-t3-oss.vercel.app
t3-env-t3-oss.vercel.app
t3-env.vercel.app
env.t3.gg

Please sign in to comment.