Skip to content

Instantly share code, notes, and snippets.

@moinulmoin
Created September 16, 2023 16:12
Show Gist options
  • Select an option

  • Save moinulmoin/abe9a3e9f9ade2d1c2fdd5a3c89784c0 to your computer and use it in GitHub Desktop.

Select an option

Save moinulmoin/abe9a3e9f9ade2d1c2fdd5a3c89784c0 to your computer and use it in GitHub Desktop.

Revisions

  1. moinulmoin created this gist Sep 16, 2023.
    78 changes: 78 additions & 0 deletions env.mjs
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,78 @@
    // ref: https://github.com/webdevcody/online-marketplace/blob/main/src/env.mjs

    import { z } from "zod";

    /**
    * Specify your server-side environment variables schema here. This way you can ensure the app isn't
    * built with invalid env vars.
    */
    const server = z.object({
    DATABASE_URL: z.string().url(),
    NODE_ENV: z.enum(["development", "test", "production"]),
    });

    /**
    * Specify your client-side environment variables schema here. This way you can ensure the app isn't
    * built with invalid env vars. To expose them to the client, prefix them with `NEXT_PUBLIC_`.
    */
    const client = z.object({
    // NEXT_PUBLIC_CLIENTVAR: z.string().min(1),
    });

    /**
    * You can't destruct `process.env` as a regular object in the Next.js edge runtimes (e.g.
    * middlewares) or client-side so we need to destruct manually.
    *
    * @type {Record<keyof z.infer<typeof server> | keyof z.infer<typeof client>, string | undefined>}
    */
    const processEnv = {
    DATABASE_URL: process.env.DATABASE_URL,
    NODE_ENV: process.env.NODE_ENV,
    // NEXT_PUBLIC_CLIENTVAR: process.env.NEXT_PUBLIC_CLIENTVAR,
    };

    // Don't touch the part below
    // --------------------------

    const merged = server.merge(client);

    /** @typedef {z.input<typeof merged>} MergedInput */
    /** @typedef {z.infer<typeof merged>} MergedOutput */
    /** @typedef {z.SafeParseReturnType<MergedInput, MergedOutput>} MergedSafeParseReturn */

    let env = /** @type {MergedOutput} */ (process.env);

    if (!!process.env.SKIP_ENV_VALIDATION == false) {
    const isServer = typeof window === "undefined";

    const parsed = /** @type {MergedSafeParseReturn} */ (
    isServer
    ? merged.safeParse(processEnv) // on server we can validate all env vars
    : client.safeParse(processEnv) // on client we can only validate the ones that are exposed
    );

    if (parsed.success === false) {
    console.error(
    "❌ Invalid environment variables:",
    parsed.error.flatten().fieldErrors,
    );
    throw new Error("Invalid environment variables");
    }

    env = new Proxy(parsed.data, {
    get(target, prop) {
    if (typeof prop !== "string") return undefined;
    // Throw a descriptive error if a server-side env var is accessed on the client
    // Otherwise it would just be returning `undefined` and be annoying to debug
    if (!isServer && !prop.startsWith("NEXT_PUBLIC_"))
    throw new Error(
    process.env.NODE_ENV === "production"
    ? "❌ Attempted to access a server-side environment variable on the client"
    : `❌ Attempted to access server-side environment variable '${prop}' on the client`,
    );
    return target[/** @type {keyof typeof target} */ (prop)];
    },
    });
    }

    export { env };